From 73fda1bcc01e1bf7c4f980b55e71ac0212838878 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 20 Nov 2024 15:00:22 +0000 Subject: [PATCH 01/20] ab#17762 --- ...ratorExtension.sln => GCPSecretManager.sln | 2 +- GCPSecretManager/GCPSecretManager.csproj | 20 +++++ GCPSecretManager/Inventory.cs | 42 ++++++++++ GCPSecretManager/Management.cs | 42 ++++++++++ SampleOrchestratorExtension/Discovery.cs | 71 ---------------- SampleOrchestratorExtension/Inventory.cs | 84 ------------------- SampleOrchestratorExtension/Management.cs | 80 ------------------ SampleOrchestratorExtension/Reenrollment.cs | 61 -------------- .../SampleOrchestratorExtension.csproj | 12 --- 9 files changed, 105 insertions(+), 309 deletions(-) rename SampleOrchestratorExtension.sln => GCPSecretManager.sln (83%) create mode 100644 GCPSecretManager/GCPSecretManager.csproj create mode 100644 GCPSecretManager/Inventory.cs create mode 100644 GCPSecretManager/Management.cs delete mode 100644 SampleOrchestratorExtension/Discovery.cs delete mode 100644 SampleOrchestratorExtension/Inventory.cs delete mode 100644 SampleOrchestratorExtension/Management.cs delete mode 100644 SampleOrchestratorExtension/Reenrollment.cs delete mode 100644 SampleOrchestratorExtension/SampleOrchestratorExtension.csproj diff --git a/SampleOrchestratorExtension.sln b/GCPSecretManager.sln similarity index 83% rename from SampleOrchestratorExtension.sln rename to GCPSecretManager.sln index 27a5c1f..a4d7ee9 100644 --- a/SampleOrchestratorExtension.sln +++ b/GCPSecretManager.sln @@ -3,7 +3,7 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 16 VisualStudioVersion = 16.0.31702.278 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SampleOrchestratorExtension", "SampleOrchestratorExtension\SampleOrchestratorExtension.csproj", "{ECFD4531-6959-431C-8D5A-8CD62301A82A}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GCPSecretManagereOrchestratorExtension", "GCPSecretManager\GCPSecretManager.csproj", "{ECFD4531-6959-431C-8D5A-8CD62301A82A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/GCPSecretManager/GCPSecretManager.csproj b/GCPSecretManager/GCPSecretManager.csproj new file mode 100644 index 0000000..40afc63 --- /dev/null +++ b/GCPSecretManager/GCPSecretManager.csproj @@ -0,0 +1,20 @@ + + + + true + net6.0;net8.0 + true + disable + + + + + + + + + Always + + + + diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs new file mode 100644 index 0000000..4750381 --- /dev/null +++ b/GCPSecretManager/Inventory.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; + +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager +{ + public class Inventory : IInventoryJobExtension + { + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Inventory"; + + //Job Entry Point + public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) + { + ILogger logger = LogHandler.GetClassLogger(this.GetType()); + logger.LogDebug($"Begin Inventory..."); + + List inventoryItems = new List(); + + try + { + } + catch (Exception ex) + { + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + } + + try + { + submitInventory.Invoke(inventoryItems); + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; + } + catch (Exception ex) + { + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + } + } + } +} \ No newline at end of file diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs new file mode 100644 index 0000000..0c146ac --- /dev/null +++ b/GCPSecretManager/Management.cs @@ -0,0 +1,42 @@ +using System; + +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Common.Enums; + +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension +{ + public class Management : IManagementJobExtension + { + public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Management"; + + public JobResult ProcessJob(ManagementJobConfiguration config) + { + ILogger logger = LogHandler.GetClassLogger(this.GetType()); + logger.LogDebug($"Begin Management..."); + + try + { + switch (config.OperationType) + { + case CertStoreOperationType.Add: + break; + case CertStoreOperationType.Remove: + break; + case CertStoreOperationType.Create: + break; + default: + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Unsupported operation: {config.OperationType.ToString()}" }; + } + } + catch (Exception ex) + { + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + } + + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; + } + } +} \ No newline at end of file diff --git a/SampleOrchestratorExtension/Discovery.cs b/SampleOrchestratorExtension/Discovery.cs deleted file mode 100644 index 7350632..0000000 --- a/SampleOrchestratorExtension/Discovery.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Generic; - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; - -using Microsoft.Extensions.Logging; - -namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension -{ - // The Discovery class implementes IAgentJobExtension and is meant to find all certificate stores based on the information passed when creating the job in KF Command - public class Discovery : IDiscoveryJobExtension - { - //Necessary to implement IDiscoveryJobExtension but not used. Leave as empty string. - public string ExtensionName => ""; - - //Job Entry Point - public JobResult ProcessJob(DiscoveryJobConfiguration config, SubmitDiscoveryUpdate submitDiscovery) - { - //METHOD ARGUMENTS... - //config - contains context information passed from KF Command to this job run: - // - // config.ServerUsername, config.ServerPassword - credentials for orchestrated server - use to authenticate to certificate store server. - // config.ClientMachine - server name or IP address of orchestrated server - // - // config.JobProperties["dirs"] - Directories to search - // config.JobProperties["extensions"] - Extensions to search - // config.JobProperties["ignoreddirs"] - Directories to ignore - // config.JobProperties["patterns"] - File name patterns to match - - - //NLog Logging to c:\CMS\Logs\CMS_Agent_Log.txt - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Discovery..."); - - //Instantiate collection of certificate store locations to pass back - List locations = new List(); - - try - { - //Code logic to: - // 1) Connect to the orchestrated server if necessary (config.CertificateStoreDetails.ClientMachine) - // 2) Custom logic to search for valid certificate stores based on passed in: - // a) Directories to search - // b) Extensions - // c) Directories to ignore - // d) File name patterns to match - // 3) Place found and validated store locations (path and file name) in "locations" collection instantiated above - } - catch (Exception ex) - { - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - - try - { - //Sends store locations back to KF command where they can be approved or rejected - submitDiscovery.Invoke(locations); - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; - } - catch (Exception ex) - { - // NOTE: if the cause of the submitInventory.Invoke exception is a communication issue between the Orchestrator server and the Command server, the job status returned here - // may not be reflected in Keyfactor Command. - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - } - } -} \ No newline at end of file diff --git a/SampleOrchestratorExtension/Inventory.cs b/SampleOrchestratorExtension/Inventory.cs deleted file mode 100644 index 22c7252..0000000 --- a/SampleOrchestratorExtension/Inventory.cs +++ /dev/null @@ -1,84 +0,0 @@ -using System; -using System.Collections.Generic; - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; - -using Microsoft.Extensions.Logging; - -namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension -{ - // The Inventory class implementes IAgentJobExtension and is meant to find all of the certificates in a given certificate store on a given server - // and return those certificates back to Keyfactor for storing in its database. Private keys will NOT be passed back to Keyfactor Command - public class Inventory : IInventoryJobExtension - { - //Necessary to implement IInventoryJobExtension but not used. Leave as empty string. - public string ExtensionName => ""; - - //Job Entry Point - public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) - { - //METHOD ARGUMENTS... - //config - contains context information passed from KF Command to this job run: - // - // config.Server.Username, config.Server.Password - credentials for orchestrated server - use to authenticate to certificate store server. - // - // config.ServerUsername, config.ServerPassword - credentials for orchestrated server - use to authenticate to certificate store server. - // config.CertificateStoreDetails.ClientMachine - server name or IP address of orchestrated server - // config.CertificateStoreDetails.StorePath - location path of certificate store on orchestrated server - // config.CertificateStoreDetails.StorePassword - if the certificate store has a password, it would be passed here - // config.CertificateStoreDetails.Properties - JSON string containing custom store properties for this specific store type - - //NLog Logging to c:\CMS\Logs\CMS_Agent_Log.txt - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Inventory..."); - - //List is the collection that the interface expects to return from this job. It will contain a collection of certificates found in the store along with other information about those certificates - List inventoryItems = new List(); - - try - { - //Code logic to: - // 1) Connect to the orchestrated server (config.CertificateStoreDetails.ClientMachine) containing the certificate store to be inventoried (config.CertificateStoreDetails.StorePath) - // 2) Custom logic to retrieve certificates from certificate store. - // 3) Add certificates (no private keys) to the collection below. If multiple certs in a store comprise a chain, the Certificates array will house multiple certs per InventoryItem. If multiple certs - // in a store comprise separate unrelated certs, there will be one InventoryItem object created per certificate. - - //**** Will need to uncomment the block below and code to the extension's specific needs. This builds the collection of certificates and related information that will be passed back to the KF Orchestrator service and then Command. - //inventoryItems.Add(new AgentCertStoreInventoryItem() - //{ - // ItemStatus = OrchestratorInventoryItemStatus.Unknown, //There are other statuses, but Command can determine how to handle new vs modified certificates - // Alias = {valueRepresentingChainIdentifier} - // PrivateKeyEntry = true|false //You will not pass the private key back, but you can identify if the main certificate of the chain contains a private key in the store - // UseChainLevel = true|false, //true if Certificates will contain > 1 certificate, main cert => intermediate CA cert => root CA cert. false if Certificates will contain an array of 1 certificate - // Certificates = //Array of single X509 certificates in Base64 string format (certificates if chain, single cert if not), something like: - // **************************** - // foreach(X509Certificate2 certificate in certificates) - // certList.Add(Convert.ToBase64String(certificate.Export(X509ContentType.Cert))); - // certList.ToArray(); - // **************************** - //}); - - } - catch (Exception ex) - { - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - - try - { - //Sends inventoried certificates back to KF Command - submitInventory.Invoke(inventoryItems); - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; - } - catch (Exception ex) - { - // NOTE: if the cause of the submitInventory.Invoke exception is a communication issue between the Orchestrator server and the Command server, the job status returned here - // may not be reflected in Keyfactor Command. - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - } - } -} \ No newline at end of file diff --git a/SampleOrchestratorExtension/Management.cs b/SampleOrchestratorExtension/Management.cs deleted file mode 100644 index a91e14b..0000000 --- a/SampleOrchestratorExtension/Management.cs +++ /dev/null @@ -1,80 +0,0 @@ -using System; - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; -using Keyfactor.Orchestrators.Common.Enums; - -using Microsoft.Extensions.Logging; - -namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension -{ - public class Management : IManagementJobExtension - { - //Necessary to implement IManagementJobExtension but not used. Leave as empty string. - public string ExtensionName => ""; - - //Job Entry Point - public JobResult ProcessJob(ManagementJobConfiguration config) - { - //METHOD ARGUMENTS... - //config - contains context information passed from KF Command to this job run: - // - // config.Server.Username, config.Server.Password - credentials for orchestrated server - use to authenticate to certificate store server. - // - // config.ServerUsername, config.ServerPassword - credentials for orchestrated server - use to authenticate to certificate store server. - // config.CertificateStoreDetails.ClientMachine - server name or IP address of orchestrated server - // config.CertificateStoreDetails.StorePath - location path of certificate store on orchestrated server - // config.CertificateStoreDetails.StorePassword - if the certificate store has a password, it would be passed here - // config.CertificateStoreDetails.Properties - JSON string containing custom store properties for this specific store type - // - // config.JobCertificate.EntryContents - Base64 encoded string representation (PKCS12 if private key is included, DER if not) of the certificate to add for Management-Add jobs. - // config.JobCertificate.Alias - optional string value of certificate alias (used in java keystores and some other store types) - // config.OpeerationType - enumeration representing function with job type. Used only with Management jobs where this value determines whether the Management job is a CREATE/ADD/REMOVE job. - // config.Overwrite - Boolean value telling the Orchestrator Extension whether to overwrite an existing certificate in a store. How you determine whether a certificate is "the same" as the one provided is AnyAgent implementation dependent - // config.JobCertificate.PrivateKeyPassword - For a Management Add job, if the certificate being added includes the private key (therefore, a pfx is passed in config.JobCertificate.EntryContents), this will be the password for the pfx. - - - //NLog Logging to c:\CMS\Logs\CMS_Agent_Log.txt - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Management..."); - - try - { - //Management jobs, unlike Discovery, Inventory, and Reenrollment jobs can have 3 different purposes: - switch (config.OperationType) - { - case CertStoreOperationType.Add: - //OperationType == Add - Add a certificate to the certificate store passed in the config object - //Code logic to: - // 1) Connect to the orchestrated server (config.CertificateStoreDetails.ClientMachine) containing the certificate store - // 2) Custom logic to add certificate to certificate store (config.CertificateStoreDetails.StorePath) possibly using alias as an identifier if applicable (config.JobCertificate.Alias). Use alias and overwrite flag (config.Overwrite) - // to determine if job should overwrite an existing certificate in the store, for example a renewal. - break; - case CertStoreOperationType.Remove: - //OperationType == Remove - Delete a certificate from the certificate store passed in the config object - //Code logic to: - // 1) Connect to the orchestrated server (config.CertificateStoreDetails.ClientMachine) containing the certificate store - // 2) Custom logic to remove the certificate in a certificate store (config.CertificateStoreDetails.StorePath), possibly using alias (config.JobCertificate.Alias) or certificate thumbprint to identify the certificate (implementation dependent) - break; - case CertStoreOperationType.Create: - //OperationType == Create - Create an empty certificate store in the provided location - //Code logic to: - // 1) Connect to the orchestrated server (config.CertificateStoreDetails.ClientMachine) where the certificate store (config.CertificateStoreDetails.StorePath) will be located - // 2) Custom logic to first check if the store already exists and add it if not. If it already exists, implementation dependent as to how to handle - error, warning, success - break; - default: - //Invalid OperationType. Return error. Should never happen though - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Unsupported operation: {config.OperationType.ToString()}" }; - } - } - catch (Exception ex) - { - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; - } - } -} \ No newline at end of file diff --git a/SampleOrchestratorExtension/Reenrollment.cs b/SampleOrchestratorExtension/Reenrollment.cs deleted file mode 100644 index e66903b..0000000 --- a/SampleOrchestratorExtension/Reenrollment.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using System.Collections.Generic; - -using Keyfactor.Logging; -using Keyfactor.Orchestrators.Extensions; - -using Microsoft.Extensions.Logging; - -namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension -{ - // The Reenrollment class implementes IAgentJobExtension and is meant to: - // 1) Generate a new public/private keypair locally - // 2) Generate a CSR from the keypair, - // 3) Submit the CSR to KF Command to enroll the certificate and retrieve the certificate back - // 4) Deploy the newly re-enrolled certificate to a certificate store - public class Reenrollment : IReenrollmentJobExtension - { - //Necessary to implement IReenrollmentJobExtension but not used. Leave as empty string. - public string ExtensionName => ""; - - //Job Entry Point - public JobResult ProcessJob(ReenrollmentJobConfiguration config, SubmitReenrollmentCSR submitReenrollment) - { - //METHOD ARGUMENTS... - //config - contains context information passed from KF Command to this job run: - // - // config.Server.Username, config.Server.Password - credentials for orchestrated server - use to authenticate to certificate store server. - // - // config.ServerUsername, config.ServerPassword - credentials for orchestrated server - use to authenticate to certificate store server. - // config.CertificateStoreDetails.ClientMachine - server name or IP address of orchestrated server - // config.CertificateStoreDetails.StorePath - location path of certificate store on orchestrated server - // config.CertificateStoreDetails.StorePassword - if the certificate store has a password, it would be passed here - // config.CertificateStoreDetails.Properties - JSON string containing custom store properties for this specific store type - // - // config.JobProperties = Dictionary of custom parameters to use in building CSR and placing enrolled certiciate in a the proper certificate store - - //NLog Logging to c:\CMS\Logs\CMS_Agent_Log.txt - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Reenrollment..."); - - try - { - //Code logic to: - // 1) Generate a new public/private keypair locally from any config.JobProperties passed - // 2) Generate a CSR from the keypair (PKCS10), - // 3) Submit the CSR to KF Command to enroll the certificate using: - // string resp = (string)submitEnrollmentRequest.Invoke(Convert.ToBase64String(PKCS10_bytes); - // X509Certificate2 cert = new X509Certificate2(Convert.FromBase64String(resp)); - // 4) Deploy the newly re-enrolled certificate (cert in #3) to a certificate store - } - catch (Exception ex) - { - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; - } - - //Status: 2=Success, 3=Warning, 4=Error - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; - } - } -} \ No newline at end of file diff --git a/SampleOrchestratorExtension/SampleOrchestratorExtension.csproj b/SampleOrchestratorExtension/SampleOrchestratorExtension.csproj deleted file mode 100644 index 9f84aa9..0000000 --- a/SampleOrchestratorExtension/SampleOrchestratorExtension.csproj +++ /dev/null @@ -1,12 +0,0 @@ - - - - netcoreapp3.1 - - - - - - - - From d46bbe09851fe6b7e402d43e1863cf2433d79174 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Mon, 25 Nov 2024 21:13:43 +0000 Subject: [PATCH 02/20] ab#17762 --- GCPSecretManager/ExceptionHandler.cs | 29 ++++++++ GCPSecretManager/GCPClient.cs | 90 ++++++++++++++++++++++++ GCPSecretManager/GCPSecretManager.csproj | 4 +- GCPSecretManager/Inventory.cs | 31 +++++++- GCPSecretManager/Management.cs | 22 +++++- GCPSecretManager/PAMUtilities.cs | 21 ++++++ 6 files changed, 192 insertions(+), 5 deletions(-) create mode 100644 GCPSecretManager/ExceptionHandler.cs create mode 100644 GCPSecretManager/GCPClient.cs create mode 100644 GCPSecretManager/PAMUtilities.cs diff --git a/GCPSecretManager/ExceptionHandler.cs b/GCPSecretManager/ExceptionHandler.cs new file mode 100644 index 0000000..376b8f5 --- /dev/null +++ b/GCPSecretManager/ExceptionHandler.cs @@ -0,0 +1,29 @@ +// Copyright 2021 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; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager +{ + class GCPException : ApplicationException + { + public GCPException(string message) : base(message) + { } + + public GCPException(string message, Exception ex) : base(message, ex) + { } + + public static string FlattenExceptionMessages(Exception ex, string message) + { + message += ex.Message + Environment.NewLine; + if (ex.InnerException != null) + message = FlattenExceptionMessages(ex.InnerException, message); + + return message; + } + } +} diff --git a/GCPSecretManager/GCPClient.cs b/GCPSecretManager/GCPClient.cs new file mode 100644 index 0000000..8d2ee2d --- /dev/null +++ b/GCPSecretManager/GCPClient.cs @@ -0,0 +1,90 @@ +using Google.Api.Gax.ResourceNames; +using Google.Cloud.SecretManager.V1; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager +{ + internal class GCPClient + { + public void AddSecret(string alias) + { + try + { + SecretManagerServiceClient client = SecretManagerServiceClient.Create(); + AccessSecretVersionRequest request = new AccessSecretVersionRequest(); + SecretName secretName = new SecretName(ProjectId, alias); + + //create secret + CreateSecretRequest secretRequest = new CreateSecretRequest(); + secretRequest.ParentAsProjectName = new ProjectName(ProjectId); + secretRequest.SecretId = alias; + secretRequest.Secret = new Secret { Replication = new Replication { Automatic = new Replication.Types.Automatic() } }; + + Secret secret = client.CreateSecret(secretRequest); + + //create new version + AddSecretVersionRequest secretVersionRequest = new AddSecretVersionRequest(); + secretVersionRequest.ParentAsSecretName = secretName; + secretVersionRequest.Payload = new SecretPayload { Data = Google.Protobuf.ByteString.CopyFromUtf8(GetPEMCert()) }; + + SecretVersion secretVersion = client.AddSecretVersion(secretVersionRequest); + } + catch (Exception ex) + { + string i = ex.Message; + } + } + + public void GetSecret(string alias) + { + SecretManagerServiceClient client = SecretManagerServiceClient.Create(); + + ListSecretVersionsRequest request = new ListSecretVersionsRequest(); + SecretName secretName = new SecretName(ProjectId, alias); + request.ParentAsSecretName = secretName; + + var response = client.ListSecretVersions(request); + foreach (var item in response) + { + + } + AccessSecretVersionRequest request2 = new AccessSecretVersionRequest(); + SecretVersionName secretVersionName = new SecretVersionName(ProjectId, alias, "latest"); + request2.SecretVersionName = secretVersionName; + AccessSecretVersionResponse version = client.AccessSecretVersion(request2); + + } + + public void GetSecrets() + { + SecretManagerServiceClient client = SecretManagerServiceClient.Create(); + + ListSecretsRequest request = new ListSecretsRequest(); + request.PageSize = 1; + request.ParentAsProjectName = ProjectName.FromProject(ProjectId); + + ListSecretsResponse? response; + + do + { + response = client.ListSecrets(request).AsRawResponses().FirstOrDefault(); + if (response == null) + break; + else + { + foreach (var item in response.Secrets) + { + string x = item.Name; + } + } + + request.PageToken = response.NextPageToken; + } while (!string.IsNullOrEmpty(response.NextPageToken)); + + } + } +} diff --git a/GCPSecretManager/GCPSecretManager.csproj b/GCPSecretManager/GCPSecretManager.csproj index 40afc63..407d58b 100644 --- a/GCPSecretManager/GCPSecretManager.csproj +++ b/GCPSecretManager/GCPSecretManager.csproj @@ -1,4 +1,4 @@ - + true @@ -10,7 +10,7 @@ - + Always diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index 4750381..fc8caa8 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -3,8 +3,9 @@ using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; - +using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; +using Newtonsoft.Json; namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager { @@ -12,16 +13,42 @@ public class Inventory : IInventoryJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Inventory"; + IPAMSecretResolver _resolver; + + public Inventory(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + //Job Entry Point public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Inventory..."); + logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); + logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); + logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); + logger.LogDebug($"Job Properties:"); + foreach (KeyValuePair keyValue in config.JobProperties ?? new Dictionary()) + { + logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); + } + + string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); + + dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); + string projectId = properties.ProjectId?.Value; + if (string.IsNullOrEmpty(projectId)) + { + string errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; + logger.LogError(errMessage); + } + List inventoryItems = new List(); try { + GCPClient client = new GCPClient } catch (Exception ex) { diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index 0c146ac..c2f0c0e 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -5,6 +5,10 @@ using Keyfactor.Orchestrators.Common.Enums; using Microsoft.Extensions.Logging; +using System.Collections.Generic; +using Grpc.Net.Client.Balancer; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Keyfactor.Extensions.Orchestrator.GCPSecretManager; namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension { @@ -12,10 +16,26 @@ public class Management : IManagementJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Management"; + IPAMSecretResolver _resolver; + + public Management(IPAMSecretResolver resolver) + { + _resolver = resolver; + } + public JobResult ProcessJob(ManagementJobConfiguration config) { ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin Management..."); + logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); + logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); + logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); + logger.LogDebug($"Job Properties:"); + foreach (KeyValuePair keyValue in config.JobProperties ?? new Dictionary()) + { + logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); + } + + string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); try { diff --git a/GCPSecretManager/PAMUtilities.cs b/GCPSecretManager/PAMUtilities.cs new file mode 100644 index 0000000..86675cd --- /dev/null +++ b/GCPSecretManager/PAMUtilities.cs @@ -0,0 +1,21 @@ +// Copyright 2024 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; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager +{ + internal class PAMUtilities + { + internal static string ResolvePAMField(IPAMSecretResolver resolver, ILogger logger, string name, string key) + { + logger.LogDebug($"Attempting to resolve PAM eligible field {name}"); + return string.IsNullOrEmpty(key) ? key : resolver.Resolve(key); + } + } +} From 4f56aad7e7047be06bed955f6d35f2d68198660e Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Mon, 2 Dec 2024 20:58:10 +0000 Subject: [PATCH 03/20] ab#17762 --- .../BaseCertificateFormatter.cs | 22 +++ .../ICertificateFormatter.cs | 18 +++ .../PEMCertificateFormatter.cs | 55 +++++++ GCPSecretManager/GCPClient.cs | 136 +++++++++++------- GCPSecretManager/Inventory.cs | 53 ++++--- GCPSecretManager/JobBase.cs | 41 ++++++ GCPSecretManager/Management.cs | 18 +-- 7 files changed, 260 insertions(+), 83 deletions(-) create mode 100644 GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs create mode 100644 GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs create mode 100644 GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs create mode 100644 GCPSecretManager/JobBase.cs diff --git a/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs new file mode 100644 index 0000000..a54e079 --- /dev/null +++ b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs @@ -0,0 +1,22 @@ +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager + +{ + abstract class BaseCertificateFormatter : ICertificateFormatter + { + internal ILogger Logger { get; set; } + + internal BaseCertificateFormatter() + { + Logger = LogHandler.GetClassLogger(this.GetType()); + } + + public abstract bool HasPrivateKey(string entry); + + public abstract string[] FormatCertificates(string entry); + + public abstract bool IsValid(string entry); + } +} diff --git a/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs new file mode 100644 index 0000000..c6e4665 --- /dev/null +++ b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager + +{ + interface ICertificateFormatter + { + bool HasPrivateKey(string entry); + + bool IsValid(string entry); + + string[] FormatCertificates(string entry); + } +} diff --git a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs new file mode 100644 index 0000000..f389f7c --- /dev/null +++ b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager + +{ + class PEMCertificateFormatter : BaseCertificateFormatter + { + private string BEGIN_DELIMITER = "-----BEGIN CERTIFICATE-----"; + private string END_DELIMITER = "-----END CERTIFICATE-----"; + private string[] PRIVATE_KEY_DELIMITERS = new string[] { "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----BEGIN RSA PRIVATE KEY-----" }; + + public override string[] FormatCertificates(string entry) + { + List rtnCertificates = new List(); + int currStart = 0; + int currEnd = 0; + + do + { + currStart = entry.IndexOf(BEGIN_DELIMITER, currEnd) + BEGIN_DELIMITER.Length; + currEnd = entry.IndexOf(END_DELIMITER, currStart); + rtnCertificates.Add(entry.Substring(currStart + BEGIN_DELIMITER.Length, currEnd - (currStart + BEGIN_DELIMITER.Length))); + currEnd++; + } + while (entry.IndexOf(END_DELIMITER, currEnd) > -1); + + return rtnCertificates.ToArray(); + } + + public override bool HasPrivateKey(string entry) + { + bool rtnValue = false; + + foreach (string privateKeyDelimiter in PRIVATE_KEY_DELIMITERS) + { + if (entry.Contains(privateKeyDelimiter, StringComparison.OrdinalIgnoreCase)) + { + rtnValue = true; + break; + } + } + + return rtnValue; + } + + public override bool IsValid(string entry) + { + return entry.Contains(BEGIN_DELIMITER, StringComparison.OrdinalIgnoreCase) && entry.Contains(END_DELIMITER, StringComparison.OrdinalIgnoreCase); + } + } +} diff --git a/GCPSecretManager/GCPClient.cs b/GCPSecretManager/GCPClient.cs index 8d2ee2d..d97af41 100644 --- a/GCPSecretManager/GCPClient.cs +++ b/GCPSecretManager/GCPClient.cs @@ -1,20 +1,102 @@ using Google.Api.Gax.ResourceNames; using Google.Cloud.SecretManager.V1; + +using Microsoft.Extensions.Logging; + +using Keyfactor.Logging; + using System; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager { internal class GCPClient { + ILogger _logger; + string ProjectId { get; set; } + SecretManagerServiceClient Client { get; set; } + + public GCPClient(string projectId) + { + _logger = LogHandler.GetClassLogger(this.GetType()); + ProjectId = projectId; + Client = SecretManagerServiceClient.Create(); + } + + public List GetCertificateNames() + { + _logger.MethodEntry(LogLevel.Debug); + + List rtnSecrets = new List(); + + ListSecretsRequest request = new ListSecretsRequest(); + request.PageSize = 50; + request.ParentAsProjectName = ProjectName.FromProject(ProjectId); + + ListSecretsResponse response; + + try + { + do + { + response = Client.ListSecrets(request).AsRawResponses().FirstOrDefault(); + if (response == null) + break; + else + { + foreach (var item in response.Secrets) + { + rtnSecrets.Add(item.Name); + } + } + + request.PageToken = response.NextPageToken; + } while (!string.IsNullOrEmpty(response.NextPageToken)); + } + catch (Exception ex) + { + _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error retrieving certificates: ")); + throw; + } + finally + { + _logger.MethodExit(LogLevel.Debug); + } + + return rtnSecrets; + } + + public string GetCertificateEntry(string name) + { + _logger.MethodEntry(LogLevel.Debug); + + string rtnValue; + + try + { + AccessSecretVersionResponse version = Client.AccessSecretVersion(new AccessSecretVersionRequest() { Name = name + "/versions/latest" }); + rtnValue = version.Payload.Data.ToStringUtf8(); + } + catch (Exception ex) + { + _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error retrieving certificate {name}: ")); + throw; + } + finally + { + _logger.MethodExit(LogLevel.Debug); + } + + return rtnValue; + } + public void AddSecret(string alias) { + _logger.MethodEntry(LogLevel.Debug); + try { - SecretManagerServiceClient client = SecretManagerServiceClient.Create(); AccessSecretVersionRequest request = new AccessSecretVersionRequest(); SecretName secretName = new SecretName(ProjectId, alias); @@ -38,53 +120,5 @@ public void AddSecret(string alias) string i = ex.Message; } } - - public void GetSecret(string alias) - { - SecretManagerServiceClient client = SecretManagerServiceClient.Create(); - - ListSecretVersionsRequest request = new ListSecretVersionsRequest(); - SecretName secretName = new SecretName(ProjectId, alias); - request.ParentAsSecretName = secretName; - - var response = client.ListSecretVersions(request); - foreach (var item in response) - { - - } - AccessSecretVersionRequest request2 = new AccessSecretVersionRequest(); - SecretVersionName secretVersionName = new SecretVersionName(ProjectId, alias, "latest"); - request2.SecretVersionName = secretVersionName; - AccessSecretVersionResponse version = client.AccessSecretVersion(request2); - - } - - public void GetSecrets() - { - SecretManagerServiceClient client = SecretManagerServiceClient.Create(); - - ListSecretsRequest request = new ListSecretsRequest(); - request.PageSize = 1; - request.ParentAsProjectName = ProjectName.FromProject(ProjectId); - - ListSecretsResponse? response; - - do - { - response = client.ListSecrets(request).AsRawResponses().FirstOrDefault(); - if (response == null) - break; - else - { - foreach (var item in response.Secrets) - { - string x = item.Name; - } - } - - request.PageToken = response.NextPageToken; - } while (!string.IsNullOrEmpty(response.NextPageToken)); - - } } } diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index fc8caa8..712691a 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using Keyfactor.Logging; +using Keyfactor.Orchestrators.Common.Enums; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Extensions.Interfaces; using Microsoft.Extensions.Logging; @@ -9,50 +10,56 @@ namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager { - public class Inventory : IInventoryJobExtension + public class Inventory : JobBase, IInventoryJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Inventory"; - IPAMSecretResolver _resolver; - public Inventory(IPAMSecretResolver resolver) { - _resolver = resolver; + Resolver = resolver; } //Job Entry Point public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpdate submitInventory) { - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); - logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); - logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); - logger.LogDebug($"Job Properties:"); + Logger = LogHandler.GetClassLogger(this.GetType()); + Logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); + Logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); + Logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); + Logger.LogDebug($"Job Properties:"); foreach (KeyValuePair keyValue in config.JobProperties ?? new Dictionary()) { - logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); - } - - string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); - - dynamic properties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties.ToString()); - string projectId = properties.ProjectId?.Value; - if (string.IsNullOrEmpty(projectId)) - { - string errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; - logger.LogError(errMessage); + Logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); } - List inventoryItems = new List(); try { - GCPClient client = new GCPClient + Initialize(config.CertificateStoreDetails); + + GCPClient client = new GCPClient(ProjectId); + List certificateNames = client.GetCertificateNames(); + foreach(string certificateName in certificateNames) + { + string certificateEntry = client.GetCertificateEntry(certificateName); + if (!CertificateFormatter.IsValid(certificateEntry)) + continue; + string[] certificateChain = CertificateFormatter.FormatCertificates(certificateEntry); + + inventoryItems.Add(new CurrentInventoryItem() + { + ItemStatus = OrchestratorInventoryItemStatus.Unknown, + Alias = certificateName.Substring(certificateName.LastIndexOf("/") + 1), + PrivateKeyEntry = CertificateFormatter.HasPrivateKey(certificateEntry), + UseChainLevel = certificateChain.Length > 1, + Certificates = certificateChain + }); + } } catch (Exception ex) { - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}:") }; } try diff --git a/GCPSecretManager/JobBase.cs b/GCPSecretManager/JobBase.cs new file mode 100644 index 0000000..57f5299 --- /dev/null +++ b/GCPSecretManager/JobBase.cs @@ -0,0 +1,41 @@ +using Keyfactor.Logging; +using Keyfactor.Orchestrators.Extensions; +using Keyfactor.Orchestrators.Extensions.Interfaces; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager +{ + public class JobBase + { + internal ICertificateFormatter CertificateFormatter { get; set; } + internal ILogger Logger { get; set; } + internal IPAMSecretResolver Resolver { get; set; } + internal string StorePassword { get; set; } + internal string ProjectId { get; set; } + internal string PasswordSecretSuffix { get; set; } + + internal void Initialize(CertificateStore certificateStoreDetails) + { + Logger.MethodEntry(LogLevel.Debug); + + StorePassword = PAMUtilities.ResolvePAMField(Resolver, Logger, "Store Password", certificateStoreDetails.StorePassword); + + dynamic properties = JsonConvert.DeserializeObject(certificateStoreDetails.Properties.ToString()); + ProjectId = properties.ProjectId?.Value; + if (string.IsNullOrEmpty(ProjectId)) + { + string errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; + Logger.LogError(errMessage); + throw new GCPException(errMessage); + } + + CertificateFormatter = GetCertificateFormatter(); + } + + internal ICertificateFormatter GetCertificateFormatter() + { + return new PEMCertificateFormatter(); + } + } +} diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index c2f0c0e..d1fe9fe 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -12,7 +12,7 @@ namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension { - public class Management : IManagementJobExtension + public class Management : JobBase, IManagementJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Management"; @@ -25,20 +25,20 @@ public Management(IPAMSecretResolver resolver) public JobResult ProcessJob(ManagementJobConfiguration config) { - ILogger logger = LogHandler.GetClassLogger(this.GetType()); - logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); - logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); - logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); - logger.LogDebug($"Job Properties:"); + Logger = LogHandler.GetClassLogger(this.GetType()); + Logger.LogDebug($"Begin {config.Capability} for job id {config.JobId}..."); + Logger.LogDebug($"Server: {config.CertificateStoreDetails.ClientMachine}"); + Logger.LogDebug($"Store Path: {config.CertificateStoreDetails.StorePath}"); + Logger.LogDebug($"Job Properties:"); foreach (KeyValuePair keyValue in config.JobProperties ?? new Dictionary()) { - logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); + Logger.LogDebug($" {keyValue.Key}: {keyValue.Value}"); } - string storePassword = PAMUtilities.ResolvePAMField(_resolver, logger, "Store Password", config.CertificateStoreDetails.StorePassword); - try { + Initialize(config.CertificateStoreDetails); + switch (config.OperationType) { case CertStoreOperationType.Add: From c30ec9c5d8f9d4619959e927fc9084cd8afe7d76 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 4 Dec 2024 20:55:27 +0000 Subject: [PATCH 04/20] ab#17762 --- .../BaseCertificateFormatter.cs | 4 +- .../ICertificateFormatter.cs | 2 +- .../PEMCertificateFormatter.cs | 96 ++++++++++++++++++- GCPSecretManager/GCPClient.cs | 57 ++++++++++- GCPSecretManager/GCPSecretManager.csproj | 4 +- GCPSecretManager/Inventory.cs | 26 ++++- GCPSecretManager/JobBase.cs | 14 ++- GCPSecretManager/Management.cs | 22 ++++- 8 files changed, 207 insertions(+), 18 deletions(-) diff --git a/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs index a54e079..f71d8c1 100644 --- a/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs @@ -15,8 +15,8 @@ internal BaseCertificateFormatter() public abstract bool HasPrivateKey(string entry); - public abstract string[] FormatCertificates(string entry); - public abstract bool IsValid(string entry); + + public abstract string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); } } diff --git a/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs index c6e4665..93d70cb 100644 --- a/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs @@ -13,6 +13,6 @@ interface ICertificateFormatter bool IsValid(string entry); - string[] FormatCertificates(string entry); + string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); } } diff --git a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs index f389f7c..ac6d969 100644 --- a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs @@ -1,8 +1,13 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; +using Keyfactor.PKI.PEM; + +using Org.BouncyCastle.Crypto; +using Org.BouncyCastle.OpenSsl; +using Org.BouncyCastle.Pkcs; namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager @@ -51,5 +56,94 @@ public override bool IsValid(string entry) { return entry.Contains(BEGIN_DELIMITER, StringComparison.OrdinalIgnoreCase) && entry.Contains(END_DELIMITER, StringComparison.OrdinalIgnoreCase); } + + public override string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword) + { + string rtnValue = string.Empty; + + if (string.IsNullOrEmpty(privateKeyPassword)) + return PemUtilities.DERToPEM(Convert.FromBase64String(certificateContents), PemUtilities.PemObjectType.Certificate); + + Pkcs12StoreBuilder builder = new Pkcs12StoreBuilder(); + Pkcs12Store pkcs12Store = builder.Build(); + using (MemoryStream ms = new MemoryStream(Convert.FromBase64String(certificateContents))) + { + pkcs12Store.Load(ms, privateKeyPassword.ToCharArray()); + } + + + + + X509CertificateEntry[] chainEntries = certificateStore.GetCertificateChain(alias); + CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[0].Certificate); + + AsymmetricKeyParameter privateKey = certificateStore.GetKey(alias).Key; + X509CertificateEntry[] certEntries = certificateStore.GetCertificateChain(alias); + AsymmetricKeyParameter publicKey = certEntries[0].Certificate.GetPublicKey(); + + if (isRSAPrivateKey) + { + TextWriter textWriter = new StringWriter(); + PemWriter pemWriter = new PemWriter(textWriter); + pemWriter.WriteObject(privateKey); + pemWriter.Writer.Flush(); + + keyString = textWriter.ToString(); + } + else + { + PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false); + + byte[] privateKeyBytes = string.IsNullOrEmpty(storePassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(storePassword); + keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(storePassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey); + } + + pemString = certConverter.ToPEM(true); + if (string.IsNullOrEmpty(SeparatePrivateKeyFilePath)) + pemString += keyString; + + if (IncludesChain) + { + for (int i = 1; i < chainEntries.Length; i++) + { + CertificateConverter chainConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[i].Certificate); + pemString += chainConverter.ToPEM(true); + } + } + + + + + string pkcs12Alias = pkcs12Store.Aliases.First(); + X509CertificateEntry[] certChain = pkcs12Store.GetCertificateChain(pkcs12Alias); + + AsymmetricKeyEntry privateKeyEntry = pkcs12Store.GetKey(pkcs12Alias); + if (!string.IsNullOrEmpty(newPassword)) + { + Pkcs8Generator pkcs8Generator = new Pkcs8Generator(privateKeyEntry.Key, Pkcs8Generator.PbeSha1_3DES) + } + + + using (StringWriter certWriter = new StringWriter()) + { + PemWriter pemWriter = new PemWriter(certWriter); + pemWriter.WriteObject(certChain[0].Certificate); + pemWriter.WriteObject(privateKey); + + if (includeChain) + { + for (int i = 1; i < certChain.Length; i++) + { + pemWriter.WriteObject(certChain[i].Certificate); + } + } + + pemWriter.Writer.Flush(); + rtnValue = certWriter.ToString(); + certWriter.Close(); + } + + return rtnValue; + } } } diff --git a/GCPSecretManager/GCPClient.cs b/GCPSecretManager/GCPClient.cs index d97af41..7047af3 100644 --- a/GCPSecretManager/GCPClient.cs +++ b/GCPSecretManager/GCPClient.cs @@ -24,10 +24,10 @@ public GCPClient(string projectId) Client = SecretManagerServiceClient.Create(); } - public List GetCertificateNames() + public List GetSecretNames() { _logger.MethodEntry(LogLevel.Debug); - + Client List rtnSecrets = new List(); ListSecretsRequest request = new ListSecretsRequest(); @@ -80,7 +80,7 @@ public string GetCertificateEntry(string name) } catch (Exception ex) { - _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error retrieving certificate {name}: ")); + _logger.LogError(GCPException.FlattenExceptionMessages(ex, $"Error retrieving certificate {name}: ")); throw; } finally @@ -120,5 +120,56 @@ public void AddSecret(string alias) string i = ex.Message; } } + + public void DeleteCertificate(string name) + { + _logger.MethodEntry(LogLevel.Debug); + + DeleteSecretRequest request = new DeleteSecretRequest() + { + SecretName = new SecretName(ProjectId, name) + }; + + try + { + Client.DeleteSecret(request); + } + catch (Exception ex) + { + _logger.LogError(GCPException.FlattenExceptionMessages(ex, $"Error deleting certificate {name}: ")); + throw; + } + finally + { + _logger.MethodExit(LogLevel.Debug); + } + } + + public bool Exists(string name) + { + _logger.MethodEntry(LogLevel.Debug); + + bool rtnValue = true; + + GetSecretRequest request = new GetSecretRequest() + { + SecretName = new SecretName(ProjectId, name) + }; + + try + { + Client.GetSecret(request); + } + catch (Grpc.Core.RpcException ex) when (ex.StatusCode == Grpc.Core.StatusCode.NotFound) + { + rtnValue = false; + } + finally + { + _logger.MethodExit(LogLevel.Debug); + } + + return rtnValue; + } } } diff --git a/GCPSecretManager/GCPSecretManager.csproj b/GCPSecretManager/GCPSecretManager.csproj index 407d58b..21cb578 100644 --- a/GCPSecretManager/GCPSecretManager.csproj +++ b/GCPSecretManager/GCPSecretManager.csproj @@ -1,4 +1,4 @@ - + true @@ -8,9 +8,11 @@ + + Always diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index 712691a..8cdab4c 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -34,15 +34,27 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd List inventoryItems = new List(); + bool hasWarnings = false; + try { Initialize(config.CertificateStoreDetails); GCPClient client = new GCPClient(ProjectId); - List certificateNames = client.GetCertificateNames(); - foreach(string certificateName in certificateNames) + List secretNames = client.GetSecretNames(); + foreach(string secretName in secretNames) { - string certificateEntry = client.GetCertificateEntry(certificateName); + string certificateEntry = string.Empty; + try + { + certificateEntry = client.GetCertificateEntry(secretName); + } + catch (Exception) + { + hasWarnings = true; + continue; + } + if (!CertificateFormatter.IsValid(certificateEntry)) continue; string[] certificateChain = CertificateFormatter.FormatCertificates(certificateEntry); @@ -50,7 +62,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd inventoryItems.Add(new CurrentInventoryItem() { ItemStatus = OrchestratorInventoryItemStatus.Unknown, - Alias = certificateName.Substring(certificateName.LastIndexOf("/") + 1), + Alias = secretName.Substring(secretName.LastIndexOf("/") + 1), PrivateKeyEntry = CertificateFormatter.HasPrivateKey(certificateEntry), UseChainLevel = certificateChain.Length > 1, Certificates = certificateChain @@ -65,7 +77,11 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd try { submitInventory.Invoke(inventoryItems); - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; + + if (hasWarnings) + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Inventory completed successfully, but one or more secrets were not able to be retrieved. The secrets that had issues may or may not be valid certificates. Please check the orchestrator log for more details.") }; + else + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } catch (Exception ex) { diff --git a/GCPSecretManager/JobBase.cs b/GCPSecretManager/JobBase.cs index 57f5299..f4040a4 100644 --- a/GCPSecretManager/JobBase.cs +++ b/GCPSecretManager/JobBase.cs @@ -14,6 +14,7 @@ public class JobBase internal string StorePassword { get; set; } internal string ProjectId { get; set; } internal string PasswordSecretSuffix { get; set; } + internal bool IncludeChain { get; set; } internal void Initialize(CertificateStore certificateStoreDetails) { @@ -21,14 +22,21 @@ internal void Initialize(CertificateStore certificateStoreDetails) StorePassword = PAMUtilities.ResolvePAMField(Resolver, Logger, "Store Password", certificateStoreDetails.StorePassword); + string errMessage = string.Empty; dynamic properties = JsonConvert.DeserializeObject(certificateStoreDetails.Properties.ToString()); - ProjectId = properties.ProjectId?.Value; - if (string.IsNullOrEmpty(ProjectId)) + + if (properties.ProjectId == null || string.IsNullOrEmpty(properties.ProjectId.Value)) { - string errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; + errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; Logger.LogError(errMessage); throw new GCPException(errMessage); } + ProjectId = properties.ProjectId.Value; + + if (properties.PasswordSecretSuffix != null) + PasswordSecretSuffix = properties.PasswordSecretSuffix.Value; + + IncludeChain = properties.IncludeChain == null || string.IsNullOrEmpty(properties.IncludeChain.Value) ? true : bool.Parse(properties.IncludeChain.Value); CertificateFormatter = GetCertificateFormatter(); } diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index d1fe9fe..cba74fc 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -39,13 +39,15 @@ public JobResult ProcessJob(ManagementJobConfiguration config) { Initialize(config.CertificateStoreDetails); + GCPClient client = new GCPClient(ProjectId); + switch (config.OperationType) { case CertStoreOperationType.Add: + PerformAdd(config, client); break; case CertStoreOperationType.Remove: - break; - case CertStoreOperationType.Create: + client.DeleteCertificate(config.JobCertificate.Alias); break; default: return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Unsupported operation: {config.OperationType.ToString()}" }; @@ -58,5 +60,21 @@ public JobResult ProcessJob(ManagementJobConfiguration config) return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } + + private void PerformAdd(ManagementJobConfiguration config, GCPClient client) + { + bool entryExists = client.Exists(config.JobCertificate.Alias); + string newPassword = string.Empty; + + if (!config.Overwrite && entryExists) + throw new GCPException($"Secret {config.JobCertificate.Alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); + + if (string.IsNullOrEmpty(StorePassword)) + newPassword = config.JobCertificate.PrivateKeyPassword; + else + newPassword = StorePassword; + + string secret = CertificateFormatter.FormatCertificateEntry(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); + } } } \ No newline at end of file From 4797eebb234e27fc3dfae4bc0a205fd71440b46e Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Thu, 5 Dec 2024 16:07:46 +0000 Subject: [PATCH 05/20] ab#17762 --- .../BaseCertificateFormatter.cs | 4 +- .../ICertificateFormatter.cs | 4 +- .../PEMCertificateFormatter.cs | 118 ++++++------------ GCPSecretManager/Inventory.cs | 2 +- 4 files changed, 42 insertions(+), 86 deletions(-) diff --git a/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs index f71d8c1..56ec3b6 100644 --- a/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/BaseCertificateFormatter.cs @@ -17,6 +17,8 @@ internal BaseCertificateFormatter() public abstract bool IsValid(string entry); - public abstract string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); + public abstract string[] ConvertSecretToCertificateChain(string entry); + + public abstract string ConvertCertificateEntryToSecret(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); } } diff --git a/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs index 93d70cb..fbf8148 100644 --- a/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/ICertificateFormatter.cs @@ -13,6 +13,8 @@ interface ICertificateFormatter bool IsValid(string entry); - string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); + string[] ConvertSecretToCertificateChain(string entry); + + string ConvertCertificateEntryToSecret(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword); } } diff --git a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs index ac6d969..d64cbdb 100644 --- a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs @@ -2,11 +2,12 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; + using Keyfactor.PKI.PEM; +using Keyfactor.PKI.PrivateKeys; +using Keyfactor.PKI.X509; using Org.BouncyCastle.Crypto; -using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Pkcs; namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager @@ -18,24 +19,6 @@ class PEMCertificateFormatter : BaseCertificateFormatter private string END_DELIMITER = "-----END CERTIFICATE-----"; private string[] PRIVATE_KEY_DELIMITERS = new string[] { "-----BEGIN PRIVATE KEY-----", "-----BEGIN ENCRYPTED PRIVATE KEY-----", "-----BEGIN RSA PRIVATE KEY-----" }; - public override string[] FormatCertificates(string entry) - { - List rtnCertificates = new List(); - int currStart = 0; - int currEnd = 0; - - do - { - currStart = entry.IndexOf(BEGIN_DELIMITER, currEnd) + BEGIN_DELIMITER.Length; - currEnd = entry.IndexOf(END_DELIMITER, currStart); - rtnCertificates.Add(entry.Substring(currStart + BEGIN_DELIMITER.Length, currEnd - (currStart + BEGIN_DELIMITER.Length))); - currEnd++; - } - while (entry.IndexOf(END_DELIMITER, currEnd) > -1); - - return rtnCertificates.ToArray(); - } - public override bool HasPrivateKey(string entry) { bool rtnValue = false; @@ -57,10 +40,26 @@ public override bool IsValid(string entry) return entry.Contains(BEGIN_DELIMITER, StringComparison.OrdinalIgnoreCase) && entry.Contains(END_DELIMITER, StringComparison.OrdinalIgnoreCase); } - public override string FormatCertificateEntry(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword) + public override string[] ConvertSecretToCertificateChain(string entry) { - string rtnValue = string.Empty; + List rtnCertificates = new List(); + int currStart = 0; + int currEnd = 0; + do + { + currStart = entry.IndexOf(BEGIN_DELIMITER, currEnd) + BEGIN_DELIMITER.Length; + currEnd = entry.IndexOf(END_DELIMITER, currStart); + rtnCertificates.Add(entry.Substring(currStart + BEGIN_DELIMITER.Length, currEnd - (currStart + BEGIN_DELIMITER.Length))); + currEnd++; + } + while (entry.IndexOf(END_DELIMITER, currEnd) > -1); + + return rtnCertificates.ToArray(); + } + + public override string ConvertCertificateEntryToSecret(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword) + { if (string.IsNullOrEmpty(privateKeyPassword)) return PemUtilities.DERToPEM(Convert.FromBase64String(certificateContents), PemUtilities.PemObjectType.Certificate); @@ -71,79 +70,32 @@ public override string FormatCertificateEntry(string certificateContents, string pkcs12Store.Load(ms, privateKeyPassword.ToCharArray()); } + string alias = pkcs12Store.Aliases.First(); + X509CertificateEntry[] certChainEntries = pkcs12Store.GetCertificateChain(alias); + CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(certChainEntries[0].Certificate); + AsymmetricKeyParameter privateKey = pkcs12Store.GetKey(alias).Key; + AsymmetricKeyParameter publicKey = certChainEntries[0].Certificate.GetPublicKey(); - X509CertificateEntry[] chainEntries = certificateStore.GetCertificateChain(alias); - CertificateConverter certConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[0].Certificate); - - AsymmetricKeyParameter privateKey = certificateStore.GetKey(alias).Key; - X509CertificateEntry[] certEntries = certificateStore.GetCertificateChain(alias); - AsymmetricKeyParameter publicKey = certEntries[0].Certificate.GetPublicKey(); - - if (isRSAPrivateKey) - { - TextWriter textWriter = new StringWriter(); - PemWriter pemWriter = new PemWriter(textWriter); - pemWriter.WriteObject(privateKey); - pemWriter.Writer.Flush(); + PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false); - keyString = textWriter.ToString(); - } - else - { - PrivateKeyConverter keyConverter = PrivateKeyConverterFactory.FromBCKeyPair(privateKey, publicKey, false); + byte[] privateKeyBytes = string.IsNullOrEmpty(newPassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(newPassword); + string keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(newPassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey); - byte[] privateKeyBytes = string.IsNullOrEmpty(storePassword) ? keyConverter.ToPkcs8BlobUnencrypted() : keyConverter.ToPkcs8Blob(storePassword); - keyString = PemUtilities.DERToPEM(privateKeyBytes, string.IsNullOrEmpty(storePassword) ? PemUtilities.PemObjectType.PrivateKey : PemUtilities.PemObjectType.EncryptedPrivateKey); - } + string pemString = certConverter.ToPEM(true); + pemString += keyString; - pemString = certConverter.ToPEM(true); - if (string.IsNullOrEmpty(SeparatePrivateKeyFilePath)) - pemString += keyString; - - if (IncludesChain) + if (includeChain) { - for (int i = 1; i < chainEntries.Length; i++) + for (int i = 1; i < certChainEntries.Length; i++) { - CertificateConverter chainConverter = CertificateConverterFactory.FromBouncyCastleCertificate(chainEntries[i].Certificate); + CertificateConverter chainConverter = CertificateConverterFactory.FromBouncyCastleCertificate(certChainEntries[i].Certificate); pemString += chainConverter.ToPEM(true); } } - - - - string pkcs12Alias = pkcs12Store.Aliases.First(); - X509CertificateEntry[] certChain = pkcs12Store.GetCertificateChain(pkcs12Alias); - - AsymmetricKeyEntry privateKeyEntry = pkcs12Store.GetKey(pkcs12Alias); - if (!string.IsNullOrEmpty(newPassword)) - { - Pkcs8Generator pkcs8Generator = new Pkcs8Generator(privateKeyEntry.Key, Pkcs8Generator.PbeSha1_3DES) - } - - - using (StringWriter certWriter = new StringWriter()) - { - PemWriter pemWriter = new PemWriter(certWriter); - pemWriter.WriteObject(certChain[0].Certificate); - pemWriter.WriteObject(privateKey); - - if (includeChain) - { - for (int i = 1; i < certChain.Length; i++) - { - pemWriter.WriteObject(certChain[i].Certificate); - } - } - - pemWriter.Writer.Flush(); - rtnValue = certWriter.ToString(); - certWriter.Close(); - } - - return rtnValue; + return pemString; } } } diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index 8cdab4c..a01a42b 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -57,7 +57,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd if (!CertificateFormatter.IsValid(certificateEntry)) continue; - string[] certificateChain = CertificateFormatter.FormatCertificates(certificateEntry); + string[] certificateChain = CertificateFormatter.ConvertSecretToCertificateChain(certificateEntry); inventoryItems.Add(new CurrentInventoryItem() { From 1935e1e75db582cb84b11f3aeb3cce9970d5bfcf Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Thu, 5 Dec 2024 18:08:38 +0000 Subject: [PATCH 06/20] ab#17762 --- GCPSecretManager/GCPClient.cs | 33 +++++++++++++++++++++------------ GCPSecretManager/Inventory.cs | 2 +- GCPSecretManager/Management.cs | 8 +++++--- 3 files changed, 27 insertions(+), 16 deletions(-) diff --git a/GCPSecretManager/GCPClient.cs b/GCPSecretManager/GCPClient.cs index 7047af3..384f340 100644 --- a/GCPSecretManager/GCPClient.cs +++ b/GCPSecretManager/GCPClient.cs @@ -27,7 +27,7 @@ public GCPClient(string projectId) public List GetSecretNames() { _logger.MethodEntry(LogLevel.Debug); - Client + List rtnSecrets = new List(); ListSecretsRequest request = new ListSecretsRequest(); @@ -91,33 +91,42 @@ public string GetCertificateEntry(string name) return rtnValue; } - public void AddSecret(string alias) + public void AddSecret(string alias, string secretContent, bool entryExists) { _logger.MethodEntry(LogLevel.Debug); try { - AccessSecretVersionRequest request = new AccessSecretVersionRequest(); SecretName secretName = new SecretName(ProjectId, alias); - //create secret - CreateSecretRequest secretRequest = new CreateSecretRequest(); - secretRequest.ParentAsProjectName = new ProjectName(ProjectId); - secretRequest.SecretId = alias; - secretRequest.Secret = new Secret { Replication = new Replication { Automatic = new Replication.Types.Automatic() } }; + if (!entryExists) + { + AccessSecretVersionRequest request = new AccessSecretVersionRequest(); + + //create secret + CreateSecretRequest secretRequest = new CreateSecretRequest(); + secretRequest.ParentAsProjectName = new ProjectName(ProjectId); + secretRequest.SecretId = alias; + secretRequest.Secret = new Secret { Replication = new Replication { Automatic = new Replication.Types.Automatic() } }; - Secret secret = client.CreateSecret(secretRequest); + Secret secret = Client.CreateSecret(secretRequest); + } //create new version AddSecretVersionRequest secretVersionRequest = new AddSecretVersionRequest(); secretVersionRequest.ParentAsSecretName = secretName; - secretVersionRequest.Payload = new SecretPayload { Data = Google.Protobuf.ByteString.CopyFromUtf8(GetPEMCert()) }; + secretVersionRequest.Payload = new SecretPayload { Data = Google.Protobuf.ByteString.CopyFromUtf8(secretContent) }; - SecretVersion secretVersion = client.AddSecretVersion(secretVersionRequest); + SecretVersion secretVersion = Client.AddSecretVersion(secretVersionRequest); } catch (Exception ex) { - string i = ex.Message; + _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error adding/replacing certificate")); + throw; + } + finally + { + _logger.MethodExit(LogLevel.Debug); } } diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index a01a42b..58a567f 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -79,7 +79,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd submitInventory.Invoke(inventoryItems); if (hasWarnings) - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Inventory completed successfully, but one or more secrets were not able to be retrieved. The secrets that had issues may or may not be valid certificates. Please check the orchestrator log for more details.") }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Warning, JobHistoryId = config.JobHistoryId, FailureMessage = $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Inventory completed successfully, but one or more secrets were not able to be retrieved. The secrets that had issues may or may not be valid certificates. Please check the orchestrator log for more details." }; else return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; } diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index cba74fc..0060016 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -63,18 +63,20 @@ public JobResult ProcessJob(ManagementJobConfiguration config) private void PerformAdd(ManagementJobConfiguration config, GCPClient client) { - bool entryExists = client.Exists(config.JobCertificate.Alias); + string alias = config.JobCertificate.Alias; + bool entryExists = client.Exists(alias); string newPassword = string.Empty; if (!config.Overwrite && entryExists) - throw new GCPException($"Secret {config.JobCertificate.Alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); + throw new GCPException($"Secret {alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); if (string.IsNullOrEmpty(StorePassword)) newPassword = config.JobCertificate.PrivateKeyPassword; else newPassword = StorePassword; - string secret = CertificateFormatter.FormatCertificateEntry(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); + string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); + client.AddSecret(alias, secret, entryExists); } } } \ No newline at end of file From 5e05571402749e2023cc77d12ec8d7ee4644b768 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Thu, 5 Dec 2024 20:46:39 +0000 Subject: [PATCH 07/20] ab#17762 --- GCPSecretManager/Management.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index 0060016..d8d01f5 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -71,12 +71,17 @@ private void PerformAdd(ManagementJobConfiguration config, GCPClient client) throw new GCPException($"Secret {alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); if (string.IsNullOrEmpty(StorePassword)) - newPassword = config.JobCertificate.PrivateKeyPassword; + { + if (!string.IsNullOrEmpty(PasswordSecretSuffix)) + newPassword = config.JobCertificate.PrivateKeyPassword; + } else newPassword = StorePassword; string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); client.AddSecret(alias, secret, entryExists); + if (!string.IsNullOrEmpty(newPassword) && string.IsNullOrEmpty(StorePassword)) + client.AddSecret(alias + PasswordSecretSuffix, newPassword, entryExists); } } } \ No newline at end of file From 04ecabbbe38fe4c6fbeec04db2014812f26b9cf3 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Fri, 6 Dec 2024 20:56:52 +0000 Subject: [PATCH 08/20] ab#17762 --- .../PEMCertificateFormatter.cs | 19 ++++ GCPSecretManager/Inventory.cs | 2 +- GCPSecretManager/JobBase.cs | 9 +- GCPSecretManager/Management.cs | 32 ++++-- GCPSecretManager/manifest.json | 14 +++ integration-manifest.json | 101 ++++++++---------- 6 files changed, 103 insertions(+), 74 deletions(-) create mode 100644 GCPSecretManager/manifest.json diff --git a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs index d64cbdb..936e557 100644 --- a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs @@ -3,6 +3,9 @@ using System.IO; using System.Linq; +using Microsoft.Extensions.Logging; + +using Keyfactor.Logging; using Keyfactor.PKI.PEM; using Keyfactor.PKI.PrivateKeys; using Keyfactor.PKI.X509; @@ -21,6 +24,8 @@ class PEMCertificateFormatter : BaseCertificateFormatter public override bool HasPrivateKey(string entry) { + Logger.MethodEntry(LogLevel.Debug); + bool rtnValue = false; foreach (string privateKeyDelimiter in PRIVATE_KEY_DELIMITERS) @@ -32,16 +37,24 @@ public override bool HasPrivateKey(string entry) } } + Logger.MethodExit(LogLevel.Debug); + return rtnValue; } public override bool IsValid(string entry) { + Logger.MethodEntry(LogLevel.Debug); + + Logger.MethodExit(LogLevel.Debug); + return entry.Contains(BEGIN_DELIMITER, StringComparison.OrdinalIgnoreCase) && entry.Contains(END_DELIMITER, StringComparison.OrdinalIgnoreCase); } public override string[] ConvertSecretToCertificateChain(string entry) { + Logger.MethodEntry(LogLevel.Debug); + List rtnCertificates = new List(); int currStart = 0; int currEnd = 0; @@ -55,11 +68,15 @@ public override string[] ConvertSecretToCertificateChain(string entry) } while (entry.IndexOf(END_DELIMITER, currEnd) > -1); + Logger.MethodExit(LogLevel.Debug); + return rtnCertificates.ToArray(); } public override string ConvertCertificateEntryToSecret(string certificateContents, string privateKeyPassword, bool includeChain, string newPassword) { + Logger.MethodEntry(LogLevel.Debug); + if (string.IsNullOrEmpty(privateKeyPassword)) return PemUtilities.DERToPEM(Convert.FromBase64String(certificateContents), PemUtilities.PemObjectType.Certificate); @@ -95,6 +112,8 @@ public override string ConvertCertificateEntryToSecret(string certificateContent } } + Logger.MethodExit(LogLevel.Debug); + return pemString; } } diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index 58a567f..1b6e4f6 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -85,7 +85,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd } catch (Exception ex) { - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error performing Inventory.") }; } } } diff --git a/GCPSecretManager/JobBase.cs b/GCPSecretManager/JobBase.cs index f4040a4..ef07846 100644 --- a/GCPSecretManager/JobBase.cs +++ b/GCPSecretManager/JobBase.cs @@ -21,18 +21,11 @@ internal void Initialize(CertificateStore certificateStoreDetails) Logger.MethodEntry(LogLevel.Debug); StorePassword = PAMUtilities.ResolvePAMField(Resolver, Logger, "Store Password", certificateStoreDetails.StorePassword); + ProjectId = certificateStoreDetails.StorePath; string errMessage = string.Empty; dynamic properties = JsonConvert.DeserializeObject(certificateStoreDetails.Properties.ToString()); - if (properties.ProjectId == null || string.IsNullOrEmpty(properties.ProjectId.Value)) - { - errMessage = "ProjectId missing or empty. Please provide a valid ProjectId in the certificate store definition."; - Logger.LogError(errMessage); - throw new GCPException(errMessage); - } - ProjectId = properties.ProjectId.Value; - if (properties.PasswordSecretSuffix != null) PasswordSecretSuffix = properties.PasswordSecretSuffix.Value; diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index d8d01f5..a4742fd 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -1,16 +1,15 @@ using System; +using System.Collections.Generic; using Keyfactor.Logging; using Keyfactor.Orchestrators.Extensions; using Keyfactor.Orchestrators.Common.Enums; - -using Microsoft.Extensions.Logging; -using System.Collections.Generic; -using Grpc.Net.Client.Balancer; using Keyfactor.Orchestrators.Extensions.Interfaces; using Keyfactor.Extensions.Orchestrator.GCPSecretManager; -namespace Keyfactor.Extensions.Orchestrator.SampleOrchestratorExtension +using Microsoft.Extensions.Logging; + +namespace Keyfactor.Extensions.Orchestrator.GCPSecretManager { public class Management : JobBase, IManagementJobExtension { @@ -55,7 +54,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } catch (Exception ex) { - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = "Custom message you want to show to show up as the error message in Job History in KF Command" }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error adding certificate for {config.JobCertificate.Alias}") }; } return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; @@ -63,12 +62,17 @@ public JobResult ProcessJob(ManagementJobConfiguration config) private void PerformAdd(ManagementJobConfiguration config, GCPClient client) { + Logger.MethodEntry(LogLevel.Debug); + string alias = config.JobCertificate.Alias; bool entryExists = client.Exists(alias); string newPassword = string.Empty; if (!config.Overwrite && entryExists) + { + Logger.MethodExit(LogLevel.Debug); throw new GCPException($"Secret {alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); + } if (string.IsNullOrEmpty(StorePassword)) { @@ -78,10 +82,18 @@ private void PerformAdd(ManagementJobConfiguration config, GCPClient client) else newPassword = StorePassword; - string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); - client.AddSecret(alias, secret, entryExists); - if (!string.IsNullOrEmpty(newPassword) && string.IsNullOrEmpty(StorePassword)) - client.AddSecret(alias + PasswordSecretSuffix, newPassword, entryExists); + try + { + string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); + client.AddSecret(alias, secret, entryExists); + if (!string.IsNullOrEmpty(newPassword) && string.IsNullOrEmpty(StorePassword)) + client.AddSecret(alias + PasswordSecretSuffix, newPassword, entryExists); + } + catch { throw; } + finally + { + Logger.MethodExit(LogLevel.Debug); + } } } } \ No newline at end of file diff --git a/GCPSecretManager/manifest.json b/GCPSecretManager/manifest.json new file mode 100644 index 0000000..fe70420 --- /dev/null +++ b/GCPSecretManager/manifest.json @@ -0,0 +1,14 @@ +{ + "extensions": { + "Keyfactor.Orchestrators.Extensions.IOrchestratorJobExtension": { + "CertStores.GCPScrtMgr.Inventory": { + "assemblypath": "GCPSecretManager.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Inventory" + }, + "CertStores.GCPScrtMgr.Management": { + "assemblypath": "GCPSecretManager.dll", + "TypeFullName": "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Management" + } + } + } +} diff --git a/integration-manifest.json b/integration-manifest.json index e72dc0f..5540063 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -1,81 +1,72 @@ { - "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", + "$schema": "https://keyfactor.github.io/v2/integration-manifest-schema.json", "integration_type": "orchestrator", - "name": "Integration Template", - "status": "prototype", - "support_level": "community", - "description": "This project is meant to be a template to quickly build a basic integration product build. Currently in dev, a work in progress,", + "name": "GCP Secret Manager Universal Orchestrator", + "status": "production", "link_github": false, "update_catalog": false, - "release_dir": "UPDATE-THIS-WITH-PATH-TO-BINARY-BUILD-FOLDER", + "support_level": "kf-supported", + "release_dir": "GCPSecretManager/bin/Release", + "release_project": "GCPSecretManager/GCPSecretManager.csproj", + "description": "This orchestrator extension manages certificates stored as secrets in Google Secret Manager.", "about": { "orchestrator": { - "UOFramework": "10.1", - "keyfactor_platform_version": "9.10", - "pam_support": false, - "win": { - "supportsCreateStore": false, - "supportsDiscovery": false, - "supportsManagementAdd": false, - "supportsManagementRemove": false, - "supportsReenrollment": false, - "supportsInventory": false - }, - "linux": { - "supportsCreateStore": false, - "supportsDiscovery": false, - "supportsManagementAdd": false, - "supportsManagementRemove": false, - "supportsReenrollment": false, - "supportsInventory": false - }, + "UOFramework": "10.4", + "pam_support": true, + "keyfactor_platform_version": "10.4", "store_types": [ { - "Name": "MyOrchestratorStoreType", - "ShortName": "MOST", - "Capability": "MOST", - "LocalStore": false, + "Name": "GCPScrtMgr", + "ShortName": "GCPScrtMgr", + "Capability": "GCPScrtMgr", + "ServerRequired": false, + "BlueprintAllowed": true, + "CustomAliasAllowed": "Required", + "PowerShell": false, + "PrivateKeyAllowed": "Optional", "SupportedOperations": { - "Add": false, + "Add": true, "Create": false, - "Discovery": true, + "Discovery": false, "Enrollment": false, - "Remove": false + "Remove": true + }, + "PasswordOptions": { + "EntrySupported": false, + "StoreRequired": true, + "Style": "Default", + "StorePassword": { + "Description": "Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information", + "IsPAMEligible": true + } }, "Properties": [ { - "Name": "CustomField1", - "DisplayName": "CustomField1", + "Name": "PasswordSecretSuffix", + "DisplayName": "Password Secret Location Suffix", "Type": "String", "DependsOn": "", - "DefaultValue": "default", - "Required": true + "DefaultValue": "", + "Required": false, + "IsPAMEligible": false, + "Description": "If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information" }, { - "Name": "CustomField2", - "DisplayName": "CustomField2", - "Type": "String", + "Name": "IncludeChain", + "DisplayName": "Include Chain", + "Type": "bool", "DependsOn": "", - "DefaultValue": null, - "Required": true + "DefaultValue": "True", + "Required": false, + "IsPAMEligible": false, + "Description": "Determines whether to include the certificate chain when adding a certificate as a secret." } ], "EntryParameters": [], - "PasswordOptions": { - "EntrySupported": false, - "StoreRequired": false, - "Style": "Default" - }, - "StorePathType": "", - "StorePathValue": "", - "PrivateKeyAllowed": "Forbidden", - "JobProperties": [], - "ServerRequired": true, - "PowerShell": false, - "BlueprintAllowed": false, - "CustomAliasAllowed": "Forbidden" + "ClientMachineDescription": "Not used", + "StorePathDescription": "The Project ID of the Google Secret Manager being managed." } ] } } -} +} \ No newline at end of file From c0fc164d58be9c2b3323317508e1a9508e0599a1 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Mon, 9 Dec 2024 17:45:20 +0000 Subject: [PATCH 09/20] ab#17762 --- .../CertificateFormatter/PEMCertificateFormatter.cs | 3 ++- GCPSecretManager/Management.cs | 4 +--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs index 936e557..6e45d07 100644 --- a/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs +++ b/GCPSecretManager/CertificateFormatter/PEMCertificateFormatter.cs @@ -55,6 +55,7 @@ public override string[] ConvertSecretToCertificateChain(string entry) { Logger.MethodEntry(LogLevel.Debug); + entry = entry.Replace(System.Environment.NewLine, string.Empty); List rtnCertificates = new List(); int currStart = 0; int currEnd = 0; @@ -63,7 +64,7 @@ public override string[] ConvertSecretToCertificateChain(string entry) { currStart = entry.IndexOf(BEGIN_DELIMITER, currEnd) + BEGIN_DELIMITER.Length; currEnd = entry.IndexOf(END_DELIMITER, currStart); - rtnCertificates.Add(entry.Substring(currStart + BEGIN_DELIMITER.Length, currEnd - (currStart + BEGIN_DELIMITER.Length))); + rtnCertificates.Add(entry.Substring(currStart, currEnd - currStart)); currEnd++; } while (entry.IndexOf(END_DELIMITER, currEnd) > -1); diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index a4742fd..2f305be 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -15,11 +15,9 @@ public class Management : JobBase, IManagementJobExtension { public string ExtensionName => "Keyfactor.Extensions.Orchestrator.GCPSecretManager.Management"; - IPAMSecretResolver _resolver; - public Management(IPAMSecretResolver resolver) { - _resolver = resolver; + Resolver = resolver; } public JobResult ProcessJob(ManagementJobConfiguration config) From fe0473b91556896cd7bde0995b5a768108aa6320 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 11 Dec 2024 19:21:04 +0000 Subject: [PATCH 10/20] ab#11762 --- GCPSecretManager/GCPClient.cs | 2 +- GCPSecretManager/Inventory.cs | 2 +- GCPSecretManager/Management.cs | 11 ++++++++--- docsource/content.md | 25 +++++++++++++++++++++++++ docsource/gcpscrtmgr.md | 1 + 5 files changed, 36 insertions(+), 5 deletions(-) create mode 100644 docsource/content.md create mode 100644 docsource/gcpscrtmgr.md diff --git a/GCPSecretManager/GCPClient.cs b/GCPSecretManager/GCPClient.cs index 384f340..1dca852 100644 --- a/GCPSecretManager/GCPClient.cs +++ b/GCPSecretManager/GCPClient.cs @@ -121,7 +121,7 @@ public void AddSecret(string alias, string secretContent, bool entryExists) } catch (Exception ex) { - _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error adding/replacing certificate")); + _logger.LogError(GCPException.FlattenExceptionMessages(ex, "Error adding/replacing certificate. ")); throw; } finally diff --git a/GCPSecretManager/Inventory.cs b/GCPSecretManager/Inventory.cs index 1b6e4f6..6299d2e 100644 --- a/GCPSecretManager/Inventory.cs +++ b/GCPSecretManager/Inventory.cs @@ -85,7 +85,7 @@ public JobResult ProcessJob(InventoryJobConfiguration config, SubmitInventoryUpd } catch (Exception ex) { - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error performing Inventory.") }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error performing Inventory. ") }; } } } diff --git a/GCPSecretManager/Management.cs b/GCPSecretManager/Management.cs index 2f305be..d1b823c 100644 --- a/GCPSecretManager/Management.cs +++ b/GCPSecretManager/Management.cs @@ -52,7 +52,7 @@ public JobResult ProcessJob(ManagementJobConfiguration config) } catch (Exception ex) { - return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error adding certificate for {config.JobCertificate.Alias}") }; + return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = GCPException.FlattenExceptionMessages(ex, $"Site {config.CertificateStoreDetails.StorePath} on server {config.CertificateStoreDetails.ClientMachine}: Error adding certificate for {config.JobCertificate.Alias}. ") }; } return new JobResult() { Result = Keyfactor.Orchestrators.Common.Enums.OrchestratorJobStatusJobResult.Success, JobHistoryId = config.JobHistoryId }; @@ -68,8 +68,10 @@ private void PerformAdd(ManagementJobConfiguration config, GCPClient client) if (!config.Overwrite && entryExists) { + string errMsg = $"Secret {alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."; + Logger.LogError(errMsg); Logger.MethodExit(LogLevel.Debug); - throw new GCPException($"Secret {alias} already exists but Overwrite set to False. Set Overwrite to True to replace the certificate."); + throw new GCPException(errMsg); } if (string.IsNullOrEmpty(StorePassword)) @@ -85,7 +87,10 @@ private void PerformAdd(ManagementJobConfiguration config, GCPClient client) string secret = CertificateFormatter.ConvertCertificateEntryToSecret(config.JobCertificate.Contents, config.JobCertificate.PrivateKeyPassword, IncludeChain, newPassword); client.AddSecret(alias, secret, entryExists); if (!string.IsNullOrEmpty(newPassword) && string.IsNullOrEmpty(StorePassword)) - client.AddSecret(alias + PasswordSecretSuffix, newPassword, entryExists); + { + bool passwordEntryExists = client.Exists(alias + PasswordSecretSuffix); + client.AddSecret(alias + PasswordSecretSuffix, newPassword, passwordEntryExists); + } } catch { throw; } finally diff --git a/docsource/content.md b/docsource/content.md new file mode 100644 index 0000000..ee2a1dc --- /dev/null +++ b/docsource/content.md @@ -0,0 +1,25 @@ +## Overview + +The Google Cloud Platform (GCP) Secret Manager Orchestrator Extension remotely manages certificates stored as secrets in Google Cloud's Secret Manager. Each certificate store set up in Keyfactor Command represents a Google Cloud project. This orchestrator extension supports the inventory and management of certificates in PEM format stored as secrets and supports the following use cases: + +* PEM encoded certificate and unencrypted or encrypted private key +* PEM encoded certificate and unencrypted or encrypted private key with full certificate chain +* PEM encoded certificate only + +For use cases including an encrypted private key, please refer to [Certificate Encryption Details](#certificate-encryption-details) for more information on handling/storing the encryption password for the private key. + +## Requirements + +The GCP Secret Manager Orchestrator Extension uses Google Application Default Credentials (ADC) for authentication. Testing of this orchestrator extension was performed using a service account, but please review [Google Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) for more information on the various ways authentication can be set up. + +The GCP project and account being used to access Secret Manager must have access to and enabled the Secret Manger API and also must have assigned to it the Secret Manager Admin role. + + +## Secret Encryption Details + +For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: + +1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. +2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. + +If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". diff --git a/docsource/gcpscrtmgr.md b/docsource/gcpscrtmgr.md new file mode 100644 index 0000000..ed37e8e --- /dev/null +++ b/docsource/gcpscrtmgr.md @@ -0,0 +1 @@ +## Overview From 571af64e580910ce1cf61358928aa6dc035fcd8b Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 11 Dec 2024 19:22:26 +0000 Subject: [PATCH 11/20] ab#17762 --- .../workflows/keyfactor-merge-store-types.yml | 27 +++++++++++++++++++ .../workflows/keyfactor-starter-workflow.yml | 20 ++++++++++++++ 2 files changed, 47 insertions(+) create mode 100644 .github/workflows/keyfactor-merge-store-types.yml create mode 100644 .github/workflows/keyfactor-starter-workflow.yml diff --git a/.github/workflows/keyfactor-merge-store-types.yml b/.github/workflows/keyfactor-merge-store-types.yml new file mode 100644 index 0000000..c70659f --- /dev/null +++ b/.github/workflows/keyfactor-merge-store-types.yml @@ -0,0 +1,27 @@ +name: Keyfactor Merge Cert Store Types +on: [workflow_dispatch] + +jobs: + get-manifest-properties: + runs-on: windows-latest + outputs: + update_catalog: ${{ steps.read-json.outputs.update_catalog }} + integration_type: ${{ steps.read-json.outputs.integration_type }} + steps: + - uses: actions/checkout@v3 + - name: Store json + id: read-json + shell: pwsh + run: | + $json = Get-Content integration-manifest.json | ConvertFrom-Json + $myvar = $json.update_catalog + echo "update_catalog=$myvar" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append + $myvar = $json.integration_type + echo "integration_type=$myvar" | Out-File -FilePath $Env:GITHUB_OUTPUT -Encoding utf8 -Append + + call-update-store-types-workflow: + needs: get-manifest-properties + if: needs.get-manifest-properties.outputs.integration_type == 'orchestrator' && (github.event_name == 'push' || github.event_name == 'workflow_dispatch') + uses: Keyfactor/actions/.github/workflows/update-store-types.yml@main + secrets: + token: ${{ secrets.UPDATE_STORE_TYPES }} diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml new file mode 100644 index 0000000..a4649f2 --- /dev/null +++ b/.github/workflows/keyfactor-starter-workflow.yml @@ -0,0 +1,20 @@ +name: Keyfactor Bootstrap Workflow + +on: + workflow_dispatch: + pull_request: + types: [opened, closed, synchronize, edited, reopened] + push: + create: + branches: + - 'release-*.*' + +jobs: + call-starter-workflow: + uses: keyfactor/actions/.github/workflows/starter.yml@3.1.2 + secrets: + token: ${{ secrets.V2BUILDTOKEN}} + APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} + scan_token: ${{ secrets.SAST_TOKEN }} From 16a0d654f859ebd5e7e18fdabe4b2ea531864578 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 11 Dec 2024 19:23:28 +0000 Subject: [PATCH 12/20] Update generated docs --- README.md | 395 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 281 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index 1843da0..a88e364 100644 --- a/README.md +++ b/README.md @@ -1,114 +1,281 @@ -# cpr-orchestrator-template - -## Template for new (Universal) Orchestrator integrations - -### Use this repository to create new integrations for new universal orchestrator integration types. - - -1. [Use this repository](#using-the-repository) -1. [Update the integration-manifest.json](#updating-the-integration-manifest.json) -1. [Add Keyfactor Bootstrap Workflow (keyfactor-bootstrap-workflow.yml)](#add-bootstrap) -1. [Create required branches](#create-required-branches) -1. [Replace template files/folders](#replace-template-files-and-folders) -1. [Create initial prerelease](#create-initial-prerelease) ---- - -#### Using the repository -1. Select the ```Use this template``` button at the top of this page -1. Update the repository name following [these guidelines](https://keyfactorinc.sharepoint.com/sites/IntegrationWiki/SitePages/GitHub-Processes.aspx#repository-naming-conventions) - 1. All repositories must be in lower-case - 1. General pattern: company-product-type - 1. e.g. hashicorp-vault-orchestator -1. Click the ```Create repository``` button - ---- - -#### Updating the integration-manifest.json - -*The following properties must be updated in the [integration-manifest.json](./integration-manifest.json)* - -Clone the repository locally, use vsdev.io, or the GitHub online editor to update the file. - -* "name": "Friendly name for the integration" - * This will be used in the readme file generation and catalog entries -* "description": "Brief description of the integration." - * This will be used in the readme file generation - * If the repository description is empty this value will be used for the repository description upon creating a release branch -* "release_dir": "PATH\\\TO\\\BINARY\\\RELEASE\\\OUTPUT\\\FOLDER" - * Path separators can be "\\\\" or "/" - * Be sure to specify the release folder name. This can be found by running a Release build and noting the output folder - * Example: "AzureAppGatewayOrchestrator\\bin\\Release" -* "about.orchestrator.platform_UOFramework": "10.1" - * Universal Orchestrator Framework version required -* "about.orchestrator.keyfactor_platform_support": "9.10" - * Command platform version required -* "about.orchestrator.pam_support": false - * If the orchestrator supports PAM change this value to true - -For each platform (win and linux) define which capabilities are present for this orchestrator extension. You must update the boolean properties for both win and linux platforms. - -* "supportsCreateStore" -* "supportsDiscovery" -* "supportsManagementAdd" -* "supportsManagementRemove" -* "supportsReenrollment" -* "supportsInventory" - -### Cert Store Definitions - -The integration-manifest.json contains cert-store definitions for use with [kfutil](https://github.com/keyfactor/kfutil). - -Instructions for creating the store type entries can be found on the [kfutil Orchestrator Store Type Integration page on Confluence](https://keyfactor.atlassian.net/wiki/x/SoBVBQ) - ---- - -#### Add Bootstrap -Add Keyfactor Bootstrap Workflow (keyfactor-bootstrap-workflow.yml). This can be copied directly from the workflow templates or through the Actions tab -* Directly: - 1. Create a file named ```.github\workflows\keyfactor-bootstrap-workflow.yml``` - 1. Copy the contents of [keyfactor/.github/workflow-templates/keyfactor-bootstrap-workflow.yml](https://raw.githubusercontent.com/Keyfactor/.github/main/workflow-templates/keyfactor-bootstrap-workflow.yml) into the file created in the previous step -* Actions tab: - 1. Navigate to the [Actions tab](./actions) in the new repository - 1. Click the ```New workflow``` button - 1. Find the ```Keyfactor Bootstrap Workflow``` and click the ```Configure``` button - 1. Click the ```Commit changes...``` button on this screen and the next to add the bootstrap workflow to the main branch - -A new build will run the tasks of a *Push* trigger on the main branch - -*Ensure there are no errors during the workflow run in the Actions tab.* - ---- - -#### Create required branches -1. Create a release branch from main: release-1.0 -1. Create a dev branch from the starting with the devops id in the format ab#\, e.g. ab#53535. - 1. For the cleanest pull request merge, create the dev branch from the release branch. - 1. Optionally, add a suffix to the branch name indicating initial release. e.g. ab#53535-initial-release - ---- - - -#### Replace template files and folders -1. Replace the contents of readme_source.md -1. Create a CHANGELOG.md file in the root of the repository indicating ```1.0: Initial release``` -1. Replace the SampleOrchestratorExtension.sln solution file and SampleOrchestratorExtension folder with your new orchestrator dotnet solution -1. Push your updates to the dev branch (ab#xxxxx) - ---- - - -#### Create initial prerelease -1. Create a pull request from the dev branch to the release-1.0 branch - - ----- - -When the repository is ready for SE Demo, change the following property: -* "status": "pilot" - -When the integration has been approved by Support and Delivery teams, change the following property: -* "status": "production" - -If the repository is ready to be published in the public catalog, the following properties must be updated: -* "update_catalog": true -* "link_github": true +

+ GCP Secret Manager Universal Orchestrator Extension +

+ +

+ +Integration Status: production +Release +Issues +GitHub Downloads (all assets, all releases) +

+ +

+ + + Support + + · + + Installation + + · + + License + + · + + Related Integrations + +

+ +## Overview + +The Google Cloud Platform (GCP) Secret Manager Orchestrator Extension remotely manages certificates stored as secrets in Google Cloud's Secret Manager. Each certificate store set up in Keyfactor Command represents a Google Cloud project. This orchestrator extension supports the inventory and management of certificates in PEM format stored as secrets and supports the following use cases: + +* PEM encoded certificate and unencrypted or encrypted private key +* PEM encoded certificate and unencrypted or encrypted private key with full certificate chain +* PEM encoded certificate only + +For use cases including an encrypted private key, please refer to [Certificate Encryption Details](#certificate-encryption-details) for more information on handling/storing the encryption password for the private key. + + + +## Compatibility + +This integration is compatible with Keyfactor Universal Orchestrator version 10.4 and later. + +## Support +The GCP Secret Manager Universal Orchestrator extension is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket with your Keyfactor representative. If you have a support issue, please open a support ticket 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. + +## Requirements & Prerequisites + +Before installing the GCP Secret Manager Universal Orchestrator extension, we recommend that you install [kfutil](https://github.com/Keyfactor/kfutil). Kfutil is a command-line tool that simplifies the process of creating store types, installing extensions, and instantiating certificate stores in Keyfactor Command. + + +The GCP Secret Manager Orchestrator Extension uses Google Application Default Credentials (ADC) for authentication. Testing of this orchestrator extension was performed using a service account, but please review [Google Application Default Credentials](https://cloud.google.com/docs/authentication/application-default-credentials) for more information on the various ways authentication can be set up. + +The GCP project and account being used to access Secret Manager must have access to and enabled the Secret Manger API and also must have assigned to it the Secret Manager Admin role. + + +## Create the GCPScrtMgr Certificate Store Type + +To use the GCP Secret Manager Universal Orchestrator extension, you **must** create the GCPScrtMgr Certificate Store Type. This only needs to happen _once_ per Keyfactor Command instance. + + + +* **Create GCPScrtMgr using kfutil**: + + ```shell + # GCPScrtMgr + kfutil store-types create GCPScrtMgr + ``` + +* **Create GCPScrtMgr manually in the Command UI**: +
Create GCPScrtMgr manually in the Command UI + + Create a store type called `GCPScrtMgr` with the attributes in the tables below: + + #### Basic Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Name | GCPScrtMgr | Display name for the store type (may be customized) | + | Short Name | GCPScrtMgr | Short display name for the store type | + | Capability | GCPScrtMgr | Store type name orchestrator will register with. Check the box to allow entry of value | + | Supports Add | ✅ Checked | Check the box. Indicates that the Store Type supports Management Add | + | Supports Remove | ✅ Checked | Check the box. Indicates that the Store Type supports Management Remove | + | Supports Discovery | 🔲 Unchecked | Indicates that the Store Type supports Discovery | + | Supports Reenrollment | 🔲 Unchecked | Indicates that the Store Type supports Reenrollment | + | Supports Create | 🔲 Unchecked | Indicates that the Store Type supports store creation | + | Needs Server | 🔲 Unchecked | Determines if a target server name is required when creating store | + | Blueprint Allowed | ✅ Checked | Determines if store type may be included in an Orchestrator blueprint | + | Uses PowerShell | 🔲 Unchecked | Determines if underlying implementation is PowerShell | + | Requires Store Password | ✅ Checked | Enables users to optionally specify a store password when defining a Certificate Store. | + | Supports Entry Password | 🔲 Unchecked | Determines if an individual entry within a store can have a password. | + + The Basic tab should look like this: + + ![GCPScrtMgr Basic Tab](docsource/images/GCPScrtMgr-basic-store-type-dialog.png) + + #### Advanced Tab + | Attribute | Value | Description | + | --------- | ----- | ----- | + | Supports Custom Alias | Required | Determines if an individual entry within a store can have a custom Alias. | + | Private Key Handling | Optional | This determines if Keyfactor can send the private key associated with a certificate to the store. Required because IIS certificates without private keys would be invalid. | + | PFX Password Style | Default | 'Default' - PFX password is randomly generated, 'Custom' - PFX password may be specified when the enrollment job is created (Requires the Allow Custom Password application setting to be enabled.) | + + The Advanced tab should look like this: + + ![GCPScrtMgr Advanced Tab](docsource/images/GCPScrtMgr-advanced-store-type-dialog.png) + + #### Custom Fields Tab + Custom fields operate at the certificate store level and are used to control how the orchestrator connects to the remote target server containing the certificate store to be managed. The following custom fields should be added to the store type: + + | Name | Display Name | Description | Type | Default Value/Options | Required | + | ---- | ------------ | ---- | --------------------- | -------- | ----------- | + | PasswordSecretSuffix | Password Secret Location Suffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | String | | 🔲 Unchecked | + | IncludeChain | Include Chain | Determines whether to include the certificate chain when adding a certificate as a secret. | bool | True | 🔲 Unchecked | + + The Custom Fields tab should look like this: + + ![GCPScrtMgr Custom Fields Tab](docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png) + + + +
+ +## Installation + +1. **Download the latest GCP Secret Manager Universal Orchestrator extension from GitHub.** + + Navigate to the [GCP Secret Manager Universal Orchestrator extension GitHub version page](https://github.com/Keyfactor/gcp-secretmanager-orchestrator/releases/latest). Refer to the compatibility matrix below to determine whether the `net6.0` or `net8.0` asset should be downloaded. Then, click the corresponding asset to download the zip archive. + | Universal Orchestrator Version | Latest .NET version installed on the Universal Orchestrator server | `rollForward` condition in `Orchestrator.runtimeconfig.json` | `gcp-secretmanager-orchestrator` .NET version to download | + | --------- | ----------- | ----------- | ----------- | + | Older than `11.0.0` | | | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net6.0` | | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `Disable` | `net6.0` | + | Between `11.0.0` and `11.5.1` (inclusive) | `net8.0` | `LatestMajor` | `net8.0` | + | `11.6` _and_ newer | `net8.0` | | `net8.0` | + + Unzip the archive containing extension assemblies to a known location. + + > **Note** If you don't see an asset with a corresponding .NET version, you should always assume that it was compiled for `net6.0`. + +2. **Locate the Universal Orchestrator extensions directory.** + + * **Default on Windows** - `C:\Program Files\Keyfactor\Keyfactor Orchestrator\extensions` + * **Default on Linux** - `/opt/keyfactor/orchestrator/extensions` + +3. **Create a new directory for the GCP Secret Manager Universal Orchestrator extension inside the extensions directory.** + + Create a new directory called `gcp-secretmanager-orchestrator`. + > The directory name does not need to match any names used elsewhere; it just has to be unique within the extensions directory. + +4. **Copy the contents of the downloaded and unzipped assemblies from __step 2__ to the `gcp-secretmanager-orchestrator` directory.** + +5. **Restart the Universal Orchestrator service.** + + Refer to [Starting/Restarting the Universal Orchestrator service](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/StarttheService.htm). + + +6. **(optional) PAM Integration** + + The GCP Secret Manager Universal Orchestrator extension is compatible with all supported Keyfactor PAM extensions to resolve PAM-eligible secrets. PAM extensions running on Universal Orchestrators enable secure retrieval of secrets from a connected PAM provider. + + To configure a PAM provider, [reference the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam) to select an extension, and follow the associated instructions to install it on the Universal Orchestrator (remote). + + +> The above installation steps can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/InstallingAgents/NetCoreOrchestrator/CustomExtensions.htm?Highlight=extensions). + + + +## Defining Certificate Stores + + + +* **Manually with the Command UI** + +
Create Certificate Stores manually in the UI + + 1. **Navigate to the _Certificate Stores_ page in Keyfactor Command.** + + Log into Keyfactor Command, toggle the _Locations_ dropdown, and click _Certificate Stores_. + + 2. **Add a Certificate Store.** + + Click the Add button to add a new Certificate Store. Use the table below to populate the **Attributes** in the **Add** form. + | Attribute | Description | + | --------- | ----------- | + | Category | Select "GCPScrtMgr" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | Not used | + | Store Path | The Project ID of the Google Secret Manager being managed. | + | Orchestrator | Select an approved orchestrator capable of managing `GCPScrtMgr` certificates. Specifically, one with the `GCPScrtMgr` capability. | + | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | IncludeChain | Determines whether to include the certificate chain when adding a certificate as a secret. | + | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + + + +
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator + + If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_. + | Attribute | Description | + | --------- | ----------- | + | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + + Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side. + + > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself. +
+ + +
+ +* **Using kfutil** + +
Create Certificate Stores with kfutil + + 1. **Generate a CSV template for the GCPScrtMgr certificate store** + + ```shell + kfutil stores import generate-template --store-type-name GCPScrtMgr --outpath GCPScrtMgr.csv + ``` + 2. **Populate the generated CSV file** + + Open the CSV file, and reference the table below to populate parameters for each **Attribute**. + | Attribute | Description | + | --------- | ----------- | + | Category | Select "GCPScrtMgr" or the customized certificate store name from the previous step. | + | Container | Optional container to associate certificate store with. | + | Client Machine | Not used | + | Store Path | The Project ID of the Google Secret Manager being managed. | + | Orchestrator | Select an approved orchestrator capable of managing `GCPScrtMgr` certificates. Specifically, one with the `GCPScrtMgr` capability. | + | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | IncludeChain | Determines whether to include the certificate chain when adding a certificate as a secret. | + | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + + + +
Attributes eligible for retrieval by a PAM Provider on the Universal Orchestrator + + If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_. + | Attribute | Description | + | --------- | ----------- | + | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + + > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself. +
+ + + 3. **Import the CSV file to create the certificate stores** + + ```shell + kfutil stores import csv --store-type-name GCPScrtMgr --file GCPScrtMgr.csv + ``` +
+ +> The content in this section can be supplimented by the [official Command documentation](https://software.keyfactor.com/Core-OnPrem/Current/Content/ReferenceGuide/Certificate%20Stores.htm?Highlight=certificate%20store). + + + + +## Secret Encryption Details + +For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: + +1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. +2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. + +If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". + + +## License + +Apache License 2.0, see [LICENSE](LICENSE). + +## Related Integrations + +See all [Keyfactor Universal Orchestrator extensions](https://github.com/orgs/Keyfactor/repositories?q=orchestrator). \ No newline at end of file From 4ef62a77fe04cacc211dd41087b82de514639d54 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 11 Dec 2024 20:26:10 +0000 Subject: [PATCH 13/20] ab#17762 --- docsource/content.md | 4 ++-- integration-manifest.json | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docsource/content.md b/docsource/content.md index ee2a1dc..2202336 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -15,11 +15,11 @@ The GCP Secret Manager Orchestrator Extension uses Google Application Default Cr The GCP project and account being used to access Secret Manager must have access to and enabled the Secret Manger API and also must have assigned to it the Secret Manager Admin role. -## Secret Encryption Details +## Certificate Encryption Details For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: 1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. 2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. -If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". +If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". Please note that if using the generated password Keyfactor Command provides and storing the password in Secret Manager, each renewal/replacement of a certificate will encrypt the private key with a new generated password, which will then be stored as a new version of the password secret. diff --git a/integration-manifest.json b/integration-manifest.json index 5540063..f3fad66 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -36,7 +36,7 @@ "StoreRequired": true, "Style": "Default", "StorePassword": { - "Description": "Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information", + "Description": "Password used to encrypt the private key of ALL certificate secrets. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information", "IsPAMEligible": true } }, @@ -49,7 +49,7 @@ "DefaultValue": "", "Required": false, "IsPAMEligible": false, - "Description": "If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information" + "Description": "If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information" }, { "Name": "IncludeChain", From 093c590f155e93d46c03a00624d2fe85fd1b2535 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 11 Dec 2024 20:28:44 +0000 Subject: [PATCH 14/20] Update generated docs --- README.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index a88e364..59bfe53 100644 --- a/README.md +++ b/README.md @@ -115,7 +115,7 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre | Name | Display Name | Description | Type | Default Value/Options | Required | | ---- | ------------ | ---- | --------------------- | -------- | ----------- | - | PasswordSecretSuffix | Password Secret Location Suffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | String | | 🔲 Unchecked | + | PasswordSecretSuffix | Password Secret Location Suffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | String | | 🔲 Unchecked | | IncludeChain | Include Chain | Determines whether to include the certificate chain when adding a certificate as a secret. | bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: @@ -193,9 +193,9 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre | Client Machine | Not used | | Store Path | The Project ID of the Google Secret Manager being managed. | | Orchestrator | Select an approved orchestrator capable of managing `GCPScrtMgr` certificates. Specifically, one with the `GCPScrtMgr` capability. | - | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | | IncludeChain | Determines whether to include the certificate chain when adding a certificate as a secret. | - | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | Store Password | Password used to encrypt the private key of ALL certificate secrets. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | @@ -204,7 +204,7 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_. | Attribute | Description | | --------- | ----------- | - | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | Store Password | Password used to encrypt the private key of ALL certificate secrets. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | Please refer to the **Universal Orchestrator (remote)** usage section ([PAM providers on the Keyfactor Integration Catalog](https://keyfactor.github.io/integrations-catalog/content/pam)) for your selected PAM provider for instructions on how to load attributes orchestrator-side. @@ -233,9 +233,9 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre | Client Machine | Not used | | Store Path | The Project ID of the Google Secret Manager being managed. | | Orchestrator | Select an approved orchestrator capable of managing `GCPScrtMgr` certificates. Specifically, one with the `GCPScrtMgr` capability. | - | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | PasswordSecretSuffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | | IncludeChain | Determines whether to include the certificate chain when adding a certificate as a secret. | - | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | Store Password | Password used to encrypt the private key of ALL certificate secrets. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | @@ -244,7 +244,7 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre If a PAM provider was installed _on the Universal Orchestrator_ in the [Installation](#Installation) section, the following parameters can be configured for retrieval _on the Universal Orchestrator_. | Attribute | Description | | --------- | ----------- | - | Store Password | Password used encrypt the private key when adding a certificate as a secret. Please see [Certificate Encryption Details ](#certificate-encryption-details) for more information | + | Store Password | Password used to encrypt the private key of ALL certificate secrets. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | > Any secret can be rendered by a PAM provider _installed on the Keyfactor Command server_. The above parameters are specific to attributes that can be fetched by an installed PAM provider running on the Universal Orchestrator server itself. @@ -262,14 +262,14 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre -## Secret Encryption Details +## Certificate Encryption Details For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: 1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. 2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. -If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". +If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". Please note that if using the generated password Keyfactor Command provides and storing the password in Secret Manager, each renewal/replacement of a certificate will encrypt the private key with a new generated password, which will then be stored as a new version of the password secret. ## License From a0e9c68da50d40db4d629721fc2c21f704061fcd Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Wed, 11 Dec 2024 21:52:39 +0000 Subject: [PATCH 15/20] ab#17762 --- docsource/content.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docsource/content.md b/docsource/content.md index 2202336..c93966d 100644 --- a/docsource/content.md +++ b/docsource/content.md @@ -17,9 +17,10 @@ The GCP project and account being used to access Secret Manager must have access ## Certificate Encryption Details -For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: +For GCP Secret Manager secrets containing private keys, the GCP Secret Manager Orchestrator Extension provides three ways to manage the certificate private key: 1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. 2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. +3. If no Store Password is set and the Password Secret Location Suffix is either missing or blank, the private key will not be encrypted. If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". Please note that if using the generated password Keyfactor Command provides and storing the password in Secret Manager, each renewal/replacement of a certificate will encrypt the private key with a new generated password, which will then be stored as a new version of the password secret. From 4586fba6435694d7252480790c077adeceb631b7 Mon Sep 17 00:00:00 2001 From: Keyfactor Date: Wed, 11 Dec 2024 21:53:48 +0000 Subject: [PATCH 16/20] Update generated docs --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 59bfe53..e02275a 100644 --- a/README.md +++ b/README.md @@ -264,10 +264,11 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre ## Certificate Encryption Details -For GCP Secret Manager secrets containing encrypted private keys, the GCP Secret Manager Orchestrator Extension provides two ways to manage the encryption password: +For GCP Secret Manager secrets containing private keys, the GCP Secret Manager Orchestrator Extension provides three ways to manage the certificate private key: 1. Using the Keyfactor Command Store Password on the certificate store definition to store the password that will be used to encrypt ALL private keys for the GCP Secret Manager project. 2. Using the Password Secret Location Suffix field on the certificate store definition to store a "suffix" that will be used in conjunction with the secret alias (name) to create a second secret in Secret Manager to store the encryption password. +3. If no Store Password is set and the Password Secret Location Suffix is either missing or blank, the private key will not be encrypted. If the Store Password has a value, this will be used to encrypt the private key during a Management Add job. If no value is set for the Store Password, the one time password that Keyfactor Command generates when triggering a Management-Add job will be used to encrypt the private key and this password will be stored as a secret in GCP Secret Manager with a name of Alias + Password Secret Location Suffix. For example, if the certificate alias is set as "Alias1" and the Password Secret Location Suffix is set as "_Key", the certificate and encrypted private key will be stored in a secret named "Alias1" and the password for the key encryption will be stored in a secret named "Alias1_Key". Please note that if using the generated password Keyfactor Command provides and storing the password in Secret Manager, each renewal/replacement of a certificate will encrypt the private key with a new generated password, which will then be stored as a new version of the password secret. From 00891aee87d050b3e5fc1324a224889c685f823c Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 16 Dec 2024 11:31:27 -0700 Subject: [PATCH 17/20] chore(docs): Generate screenshots Signed-off-by: Hayden Roszell --- .../GCPScrtMgr-advanced-store-type-dialog.png | Bin 0 -> 41690 bytes .../GCPScrtMgr-basic-store-type-dialog.png | Bin 0 -> 53404 bytes ...PScrtMgr-custom-fields-store-type-dialog.png | Bin 0 -> 32786 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 docsource/images/GCPScrtMgr-advanced-store-type-dialog.png create mode 100644 docsource/images/GCPScrtMgr-basic-store-type-dialog.png create mode 100644 docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png diff --git a/docsource/images/GCPScrtMgr-advanced-store-type-dialog.png b/docsource/images/GCPScrtMgr-advanced-store-type-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..06bc1330ff9bdae62340242f418755b1824507de GIT binary patch literal 41690 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μ#BzJGyz8CTc(0bZ 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 z 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

HPBBe3 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 literal 0 HcmV?d00001 diff --git a/docsource/images/GCPScrtMgr-basic-store-type-dialog.png b/docsource/images/GCPScrtMgr-basic-store-type-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..79beaa568b071466150e3a978c3e72bf3ddd4440 GIT binary patch literal 53404 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 literal 0 HcmV?d00001 diff --git a/docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png b/docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png new file mode 100644 index 0000000000000000000000000000000000000000..ffb4b621e9d9a8cb9588fedad2a79d5ab5505a22 GIT binary patch literal 32786 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 RK8}S&SwZ7u=I>{&{|k308>0XK literal 0 HcmV?d00001 From ab97999cf6285807c292eb62ca2ff51a218a4af7 Mon Sep 17 00:00:00 2001 From: Hayden Roszell Date: Mon, 13 Jan 2025 12:21:13 -0700 Subject: [PATCH 18/20] chore(docs): Regenerate screenshots Signed-off-by: Hayden Roszell --- ...crtMgr-custom-fields-store-type-dialog.png | Bin 32786 -> 32211 bytes integration-manifest.json | 138 +++++++++--------- 2 files changed, 69 insertions(+), 69 deletions(-) diff --git a/docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png b/docsource/images/GCPScrtMgr-custom-fields-store-type-dialog.png index ffb4b621e9d9a8cb9588fedad2a79d5ab5505a22..a961f5168d6922badc4e6f132bde95a0eaac8b67 100644 GIT binary patch delta 13314 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�HGFu_U3X@&B`i!IpiXAQOHMKs3JS@peQ< zX?ni*MVZDXaBo!*8IO%u;1Ju>t&a!R)yE`7TW~~5*GQA8_oUboFc|x+$SZl>ZF9YO zx|0?xoilLS1oVk$GZ_E!HNmdX?E?^X{SmC6#LkjFkoCKwiYa2D7!&W9uoiHV!I4zK zI+ulGbVxGw@yt3=z_JsA9TUGjDmDFaEez-pLKG~nOMlq-l3zqb18@nVHT>dFRwk{` zf;09Ds*i);R&7ygE7@gI@swm&FKf>-clSy=l8omcw*Dcihf*bn{g>+DwEZpchPAP0 z$AOv|#QCc+F)^PBHCa%d8r$2YIyw}@HWd^sJ3rj^@y95snH*glrJsyAcGJ<8gy1TT zu}*aj}KSV zjELVg5QERYn}V9Poju29!4aq2Ha26pwgI0O8L)9g0tKQU;g5@k!&SLqLHddZO!8#ra=_i^N3x_4jI;$B(m~K4ZT45I!-DE*g0CA10=c zSxO8U`|<3*I=XUiXaL}Nw7J=iHWJwSt-Y{IPGMw(^51TQXWO(mj3DU<$oLw9pK=fY zz?VtB)ACjo0d;|P;+h&x;SEN$8jM?Ql)oTXxGusp9a(*s-C!h~d8{t;g|L zhkCFxm5ptV(MCbpL+xVV=s2L8x0N4DXe7yGf$2a2j!_G60cvV?r+TAHm79<(@4p6M zZ4V?LG*sDuLx*qZ>(>?5om^!65f+xjr&a%>q+q4ppg!IuB`qW_F1K(Sfq!3o_q-EpTs>JXq^pj4<@Z!a%tybRBqWWUwKymci5^X9`Vg zbfP>az8to+v_wTgc|%YueOImOmz&QsSRlu-7T+S2X<@czO3@)MpPSm++6=|Vh3vSZ zy3zD4L&mElz=&RlgmfL^4oHP8Q@wKK3eX3gp(;n%`hE4kJ|98Y#jM^yV)}kqI?Oco z2tzF|FO~#nK({5F^9l;21R?UWDh~>H&yUY9jwEr@JCfyVo7+rr9(jwnIT7k2$s|hL zaZr4BXSFjbGeTiY4dO>UuDEjnWk)PZGAJ_5gQFU^wqMK3(qz>>XrVfAG|z*A^zDO0 z?L`^_{UvO>l^GYnAAt8E3L1lt8_Lklyh#jxeUw|l6y6)r)7FlJ7Hb!v=t`XfiprB` z*%mukY7yL5WQOTTlC=lgWL#kr2@>NnD*l?q%UB_!bl^S=akd4~m|j>Y1V04y&@9-P zaFm%OLN?pSWcnCU&fLeKq%ER-dAj7f*yxy;ERYIz#iB#9zLQ~6jQss94of1IS+)s$ z$!Z+WUHft&G;2+f3ZiGN-y*`6N+B>#@wt4bPIxX3*9

YHE^J6~+g}eODfn>CS}w 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 Jy8ZV*{|keDOiTa( delta 13889 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 d9JC>G%k5nrFtjfLa?8YoQP#YffBl~){|oN~OW^ Date: Mon, 13 Jan 2025 19:22:19 +0000 Subject: [PATCH 19/20] Update generated docs --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e02275a..3e8f266 100644 --- a/README.md +++ b/README.md @@ -116,7 +116,7 @@ To use the GCP Secret Manager Universal Orchestrator extension, you **must** cre | Name | Display Name | Description | Type | Default Value/Options | Required | | ---- | ------------ | ---- | --------------------- | -------- | ----------- | | PasswordSecretSuffix | Password Secret Location Suffix | If storing a certificate with an encrypted private key, this is the suffix to add to the certificate (secret) alias name where the encrypted private key password will be stored. Please see [Certificate Encryption Details](#certificate-encryption-details) for more information | String | | 🔲 Unchecked | - | IncludeChain | Include Chain | Determines whether to include the certificate chain when adding a certificate as a secret. | bool | True | 🔲 Unchecked | + | IncludeChain | Include Chain | Determines whether to include the certificate chain when adding a certificate as a secret. | Bool | True | 🔲 Unchecked | The Custom Fields tab should look like this: From b80e91caecb8feddc84df43927fa22a07f002966 Mon Sep 17 00:00:00 2001 From: Lee Fine Date: Mon, 13 Jan 2025 20:35:23 +0000 Subject: [PATCH 20/20] ab#17762 --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..e577aae --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +v1.0.0 +- Initial Version