diff --git a/.github/workflows/keyfactor-bootstrap-workflow.yml b/.github/workflows/keyfactor-bootstrap-workflow.yml new file mode 100644 index 0000000..6d8de53 --- /dev/null +++ b/.github/workflows/keyfactor-bootstrap-workflow.yml @@ -0,0 +1,19 @@ +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@v2 + secrets: + token: ${{ secrets.V2BUILDTOKEN}} + APPROVE_README_PUSH: ${{ secrets.APPROVE_README_PUSH}} + gpg_key: ${{ secrets.KF_GPG_PRIVATE_KEY }} + gpg_pass: ${{ secrets.KF_GPG_PASSPHRASE }} diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml deleted file mode 100644 index 4715a85..0000000 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Starter Workflow -on: [workflow_dispatch, push, pull_request] - -jobs: - call-create-github-release-workflow: - uses: Keyfactor/actions/.github/workflows/github-release.yml@main - - call-dotnet-build-and-release-workflow: - needs: [call-create-github-release-workflow] - uses: Keyfactor/actions/.github/workflows/dotnet-build-and-release.yml@main - with: - release_version: ${{ needs.call-create-github-release-workflow.outputs.release_version }} - release_url: ${{ needs.call-create-github-release-workflow.outputs.release_url }} - release_dir: GcpCertManager/bin/Release/netcoreapp3.1 # TODO: set build output directory to upload as a release, relative to checkout workspace - secrets: - token: ${{ secrets.PRIVATE_PACKAGE_ACCESS }} - - call-generate-readme-workflow: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: Keyfactor/actions/.github/workflows/generate-readme.yml@main - - call-update-catalog-workflow: - if: github.event_name == 'push' || github.event_name == 'workflow_dispatch' - uses: Keyfactor/actions/.github/workflows/update-catalog.yml@main - secrets: - token: ${{ secrets.SDK_SYNC_PAT }} diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..09d4bf6 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,2 @@ +v1.0.2 +- Initial Public Version \ No newline at end of file diff --git a/GcpCertManager.sln b/GcpCertManager.sln index 9be5e1f..17b5932 100644 --- a/GcpCertManager.sln +++ b/GcpCertManager.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30717.126 +# Visual Studio Version 17 +VisualStudioVersion = 17.3.32929.385 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GcpCertManager", "GcpCertManager\GcpCertManager.csproj", "{33FBC5A1-3466-4F10-B9A6-7186F804A65A}" EndProject @@ -13,8 +13,6 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "images", "images", "{6302034E-DF8C-4B65-AC36-CED24C068999}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GcpCertManagerTestConsole", "GcpCertManagerTestConsole\GcpCertManagerTestConsole.csproj", "{FFF21E91-1820-4090-922B-A78D5CC38D7B}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -25,10 +23,6 @@ Global {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Debug|Any CPU.Build.0 = Debug|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.ActiveCfg = Release|Any CPU {33FBC5A1-3466-4F10-B9A6-7186F804A65A}.Release|Any CPU.Build.0 = Release|Any CPU - {FFF21E91-1820-4090-922B-A78D5CC38D7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {FFF21E91-1820-4090-922B-A78D5CC38D7B}.Debug|Any CPU.Build.0 = Debug|Any CPU - {FFF21E91-1820-4090-922B-A78D5CC38D7B}.Release|Any CPU.ActiveCfg = Release|Any CPU - {FFF21E91-1820-4090-922B-A78D5CC38D7B}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/GcpCertManager/Client/GcpCertificateManagerClient.cs b/GcpCertManager/Client/GcpCertificateManagerClient.cs index c7f287b..5155204 100644 --- a/GcpCertManager/Client/GcpCertificateManagerClient.cs +++ b/GcpCertManager/Client/GcpCertificateManagerClient.cs @@ -3,6 +3,14 @@ using Google.Apis.Auth.OAuth2; using Google.Apis.CertificateManager.v1; using Google.Apis.Services; +using Google.Apis.Iam.v1; +using Google.Apis.Iam.v1.Data; +using System.Text; +using System; + +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; + namespace Keyfactor.Extensions.Orchestrator.GcpCertManager.Client { @@ -10,16 +18,29 @@ public class GcpCertificateManagerClient { public CertificateManagerService GetGoogleCredentials(string credentialFileName) { + ILogger _logger = LogHandler.GetClassLogger(); + //Credentials file needs to be in the same location of the executing assembly - var strExeFilePath = Assembly.GetExecutingAssembly().Location; - var strWorkPath = Path.GetDirectoryName(strExeFilePath); - var strSettingsJsonFilePath = Path.Combine(strWorkPath ?? string.Empty, credentialFileName); + GoogleCredential credentials; - var stream = new FileStream(strSettingsJsonFilePath, - FileMode.Open - ); + if (!string.IsNullOrEmpty(credentialFileName)) + { + _logger.LogDebug("Has credential file name"); + var strExeFilePath = Assembly.GetExecutingAssembly().Location; + var strWorkPath = Path.GetDirectoryName(strExeFilePath); + var strSettingsJsonFilePath = Path.Combine(strWorkPath ?? string.Empty, credentialFileName); - var credentials = GoogleCredential.FromStream(stream); + var stream = new FileStream(strSettingsJsonFilePath, + FileMode.Open + ); + + credentials = GoogleCredential.FromStream(stream); + } + else + { + _logger.LogDebug("No credential file name"); + credentials = GoogleCredential.GetApplicationDefaultAsync().Result; + } var service = new CertificateManagerService(new BaseClientService.Initializer { @@ -28,5 +49,21 @@ public CertificateManagerService GetGoogleCredentials(string credentialFileName) return service; } + + public ServiceAccountKey CreateServiceAccountKey(string serviceAccountEmail) + { + GoogleCredential credential = GoogleCredential.GetApplicationDefault().CreateScoped(IamService.Scope.CloudPlatform); + IamService service = new IamService(new IamService.Initializer + { + HttpClientInitializer = credential + }); + + var key = service.Projects.ServiceAccounts.Keys.Create(new CreateServiceAccountKeyRequest(), "projects/-/serviceAccounts/" + serviceAccountEmail).Execute(); + + byte[] valueBytes = System.Convert.FromBase64String(key.PrivateKeyData); + string jsonKeyContent = Encoding.UTF8.GetString(valueBytes); + + return key; + } } } \ No newline at end of file diff --git a/GcpCertManager/GcpCertManager.csproj b/GcpCertManager/GcpCertManager.csproj index cb347ff..e9e15de 100644 --- a/GcpCertManager/GcpCertManager.csproj +++ b/GcpCertManager/GcpCertManager.csproj @@ -1,6 +1,7 @@ + false netcoreapp3.1 Keyfactor.Extensions.Orchestrator.GcpCertManager true @@ -18,12 +19,11 @@ - + - diff --git a/GcpCertManager/Jobs/Inventory.cs b/GcpCertManager/Jobs/Inventory.cs index 398d676..64ca851 100644 --- a/GcpCertManager/Jobs/Inventory.cs +++ b/GcpCertManager/Jobs/Inventory.cs @@ -22,7 +22,7 @@ public Inventory(ILogger logger) _logger = logger; } - public string ExtensionName => "GcpCertManager"; + public string ExtensionName => ""; public JobResult ProcessJob(InventoryJobConfiguration jobConfiguration, SubmitInventoryUpdate submitInventoryUpdate) @@ -44,21 +44,20 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven try { _logger.MethodEntry(LogLevel.Debug); - _logger.LogTrace($"Inventory Config {JsonConvert.SerializeObject(config)}"); - _logger.LogTrace( - $"Client Machine: {config.CertificateStoreDetails.ClientMachine} ApiKey: {config.ServerPassword}"); - var storeProps = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + StoreProperties storeProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, new JsonSerializerSettings {DefaultValueHandling = DefaultValueHandling.Populate}); + storeProperties.ProjectId = config.CertificateStoreDetails.ClientMachine; - _logger.LogTrace($"Store Properties: {JsonConvert.SerializeObject(storeProps)}"); + _logger.LogTrace($"Store Properties:"); + _logger.LogTrace($" Location: {storeProperties.Location}"); + _logger.LogTrace($" Project Id: {storeProperties.ProjectId}"); + _logger.LogTrace($" Service Account Key Path: {storeProperties.ServiceAccountKey}"); - var client = new GcpCertificateManagerClient(); _logger.LogTrace("Getting Credentials from Google..."); - var svc = client.GetGoogleCredentials(config.CertificateStoreDetails.ClientMachine); + var svc = new GcpCertificateManagerClient().GetGoogleCredentials(storeProperties.ServiceAccountKey); _logger.LogTrace("Got Credentials from Google"); - var warningFlag = false; var sb = new StringBuilder(); sb.Append(""); @@ -66,52 +65,46 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven var nextPageToken = string.Empty; //todo support labels - if (storeProps != null) - foreach (var location in storeProps.Location.Split(',')) - { - var storePath = $"projects/{config.CertificateStoreDetails.StorePath}/locations/{location}"; - do - { - var certificatesRequest = - svc.Projects.Locations.Certificates.List(storePath); - certificatesRequest.Filter = "pemCertificate!=\"\""; - certificatesRequest.PageSize = 100; - if (nextPageToken?.Length > 0) certificatesRequest.PageToken = nextPageToken; - - var certificatesResponse = certificatesRequest.Execute(); - _logger.LogTrace( - $"certificatesResponse: {JsonConvert.SerializeObject(certificatesResponse)}"); - - nextPageToken = null; - //Debug Write Certificate List Response from Google Cert Manager - if (certificatesResponse?.Certificates != null) - inventoryItems.AddRange(certificatesResponse.Certificates.Select( - c => - { - try - { - _logger.LogTrace( - $"Building Cert List Inventory Item Alias: {c.Name} Pem: {c.PemCertificate} Private Key: dummy (from PA API)"); - return BuildInventoryItem(c.Name, c.PemCertificate, - true, storePath, svc, - storeProps - .ProjectNumber); //todo figure out how to see if private key exists not in Google Api return - } - catch - { - _logger.LogWarning( - $"Could not fetch the certificate: {c?.Name} associated with description {c?.Description}."); - sb.Append( - $"Could not fetch the certificate: {c?.Name} associated with issuer {c?.Description}.{Environment.NewLine}"); - warningFlag = true; - return new CurrentInventoryItem(); - } - }).Where(acsii => acsii?.Certificates != null).ToList()); + var storePath = $"projects/{storeProperties.ProjectId}/locations/{storeProperties.Location}"; - if (certificatesResponse?.NextPageToken?.Length > 0) - nextPageToken = certificatesResponse.NextPageToken; - } while (nextPageToken?.Length > 0); - } + do + { + var certificatesRequest = + svc.Projects.Locations.Certificates.List(storePath); + certificatesRequest.Filter = "pemCertificate!=\"\""; + certificatesRequest.PageSize = 100; + if (nextPageToken?.Length > 0) certificatesRequest.PageToken = nextPageToken; + + var certificatesResponse = certificatesRequest.Execute(); + _logger.LogTrace( + $"certificatesResponse: {JsonConvert.SerializeObject(certificatesResponse)}"); + + nextPageToken = null; + //Debug Write Certificate List Response from Google Cert Manager + if (certificatesResponse?.Certificates != null) + inventoryItems.AddRange(certificatesResponse.Certificates.Select( + c => + { + try + { + _logger.LogTrace( + $"Building Cert List Inventory Item Alias: {c.Name} Pem: {c.PemCertificate} Private Key: dummy (from PA API)"); + return BuildInventoryItem(c.Name, c.PemCertificate, + true, storePath, svc); + } + catch + { + _logger.LogWarning( + $"Could not fetch the certificate: {c?.Name} associated with description {c?.Description}."); + sb.Append( + $"Could not fetch the certificate: {c?.Name} associated with issuer {c?.Description}.{Environment.NewLine}"); + warningFlag = true; + return new CurrentInventoryItem(); + } + }).Where(acsii => acsii?.Certificates != null).ToList()); + + nextPageToken = certificatesResponse.NextPageToken; + } while (nextPageToken?.Length > 0); _logger.LogTrace("Submitting Inventory To Keyfactor via submitInventory.Invoke"); submitInventory.Invoke(inventoryItems); @@ -139,7 +132,9 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven } catch (GoogleApiException e) { - var googleError = e.Error.ErrorResponseContent; + var googleError = e.Error?.ErrorResponseContent + " " + LogHandler.FlattenException(e); + + _logger.LogError($"PerformInventory Error: {LogHandler.FlattenException(e)}"); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -156,7 +151,7 @@ private JobResult PerformInventory(InventoryJobConfiguration config, SubmitInven } protected virtual CurrentInventoryItem BuildInventoryItem(string alias, string certPem, bool privateKey, - string storePath, CertificateManagerService svc, string projectNumber) + string storePath, CertificateManagerService svc) { try { @@ -166,16 +161,8 @@ protected virtual CurrentInventoryItem BuildInventoryItem(string alias, string c //1. Look up certificate map entries based on certificate name var certAttributes = GetCertificateAttributes(storePath); var modAlias = alias.Split('/')[5]; - var mapSettings = GetMapSettings(storePath, modAlias, svc, projectNumber); - - _logger.LogTrace($"Got modAlias: {modAlias}, certAttributes and mapSettings"); - if (mapSettings != null && mapSettings.ContainsKey("Certificate Map Name") && - mapSettings["Certificate Map Name"]?.Length > 0) - modAlias = mapSettings["Certificate Map Name"] + "/" + mapSettings["Certificate Map Entry Name"] + - "/" + modAlias; - - _logger.LogTrace($"Got modAlias after map additions: {modAlias}"); + _logger.LogTrace($"Got modAlias: {modAlias}"); var acsi = new CurrentInventoryItem { @@ -220,57 +207,5 @@ protected Dictionary GetCertificateAttributes(string storePath) throw; } } - - - protected Dictionary GetMapSettings(string storePath, string certificateName, - CertificateManagerService svc, string projectNumber) - { - try - { - _logger.MethodEntry(); - var locationName = storePath.Split('/')[3]; - var siteSettingsDict = new Dictionary(); - var certName = $"projects/{projectNumber}/locations/{locationName}/certificates/{certificateName}"; - - _logger.LogTrace($"certName: {certName}"); - - //Loop through list of maps and map entries until you find the certificate - var mapListRequest = - svc.Projects.Locations.CertificateMaps.List(storePath); - - var mapListResponse = mapListRequest.Execute(); - _logger.LogTrace( - $"mapListResponse: {JsonConvert.SerializeObject(mapListResponse)}"); - - if (mapListResponse?.CertificateMaps != null) - foreach (var map in mapListResponse.CertificateMaps) - { - var mapEntryListRequest = - svc.Projects.Locations.CertificateMaps.CertificateMapEntries.List(map.Name); - mapEntryListRequest.Filter = $"certificates:\"{certName}\""; - var mapEntryListResponse = mapEntryListRequest.Execute(); - _logger.LogTrace( - $"mapEntryListResponse: {JsonConvert.SerializeObject(mapEntryListResponse)}"); - - if (mapEntryListResponse?.CertificateMapEntries?.Count > 0) - { - var mapEntry = mapEntryListResponse.CertificateMapEntries[0]; - _logger.LogTrace($"mapEntry: {mapEntry}"); - siteSettingsDict.Add("Certificate Map Name", map.Name.Split('/')[5]); - siteSettingsDict.Add("Certificate Map Entry Name", mapEntry.Name.Split('/')[7]); - _logger.MethodExit(); - return siteSettingsDict; - } - } - - _logger.MethodExit(); - return siteSettingsDict; - } - catch (Exception e) - { - _logger.LogError($"Error Occurred in Inventory.GetMapSettings: {LogHandler.FlattenException(e)}"); - throw; - } - } } } \ No newline at end of file diff --git a/GcpCertManager/Jobs/Management.cs b/GcpCertManager/Jobs/Management.cs index 8281836..c08cb83 100644 --- a/GcpCertManager/Jobs/Management.cs +++ b/GcpCertManager/Jobs/Management.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Drawing; using System.IO; using System.Linq; using System.Text; @@ -16,6 +17,7 @@ using Org.BouncyCastle.OpenSsl; using Org.BouncyCastle.Pkcs; using Org.BouncyCastle.X509; +using static Org.BouncyCastle.Math.EC.ECCurve; namespace Keyfactor.Extensions.Orchestrator.GcpCertManager.Jobs { @@ -24,6 +26,9 @@ public class Management : IManagementJobExtension private static readonly string certStart = "-----BEGIN CERTIFICATE-----\n"; private static readonly string certEnd = "\n-----END CERTIFICATE-----"; + private const int OPERATION_MAX_WAIT_MILLISECONDS = 300000; + private const int OPERATION_INTERVAL_WAIT_MILLISECONDS = 5000; + private static readonly Func Pemify = ss => ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + Pemify(ss.Substring(64)); @@ -36,27 +41,17 @@ public Management(ILogger logger) protected internal virtual AsymmetricKeyEntry KeyEntry { get; set; } - protected internal string MapName { get; set; } - - protected internal string MapEntryName { get; set; } - protected internal string CertificateName { get; set; } - public string ExtensionName => "GcpCertManager"; + public string ExtensionName => ""; public JobResult ProcessJob(ManagementJobConfiguration jobConfiguration) { try { - _logger.MethodEntry(); - MapName = GetMapSettingsFromAlias(jobConfiguration.JobCertificate.Alias, "map"); - _logger.LogTrace($"MapName: {MapName}"); - MapEntryName = GetMapSettingsFromAlias(jobConfiguration.JobCertificate.Alias, "mapentry"); - _logger.LogTrace($"MapEntryName: {MapEntryName}"); - CertificateName = GetMapSettingsFromAlias(jobConfiguration.JobCertificate.Alias, "certificate"); - _logger.LogTrace($"CertificateName: {CertificateName}"); - _logger.MethodExit(); + _logger.MethodEntry(LogLevel.Debug); + return PerformManagement(jobConfiguration); } catch (Exception e) @@ -71,6 +66,23 @@ private JobResult PerformManagement(ManagementJobConfiguration config) try { _logger.MethodEntry(); + + StoreProperties storeProperties = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, + new JsonSerializerSettings { DefaultValueHandling = DefaultValueHandling.Populate }); + storeProperties.ProjectId = config.CertificateStoreDetails.ClientMachine; + + _logger.LogTrace($"Store Properties:"); + _logger.LogTrace($" Location: {storeProperties.Location}"); + _logger.LogTrace($" Project Id: {storeProperties.ProjectId}"); + _logger.LogTrace($" Service Account Key Path: {storeProperties.ServiceAccountKey}"); + + _logger.LogTrace("Getting Credentials from Google..."); + var svc = new GcpCertificateManagerClient().GetGoogleCredentials(storeProperties.ServiceAccountKey); + _logger.LogTrace("Got Credentials from Google"); + + var storePath = $"projects/{storeProperties.ProjectId}/locations/{storeProperties.Location}"; + CertificateName = config.JobCertificate.Alias; + var complete = new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -79,17 +91,18 @@ private JobResult PerformManagement(ManagementJobConfiguration config) "Invalid Management Operation" }; - if (config.OperationType.ToString() == "Add") - { - _logger.LogTrace("Adding..."); - _logger.LogTrace($"Add Config Json {JsonConvert.SerializeObject(config)}"); - complete = PerformAddition(config); - } - else if (config.OperationType.ToString() == "Remove") + switch (config.OperationType) { - _logger.LogTrace("Removing..."); - _logger.LogTrace($"Remove Config Json {JsonConvert.SerializeObject(config)}"); - complete = PerformRemoval(config); + case CertStoreOperationType.Add: + _logger.LogTrace("Adding..."); + complete = PerformAddition(svc, config, storePath); + break; + case CertStoreOperationType.Remove: + _logger.LogTrace("Removing..."); + complete = PerformRemoval(svc, config, storePath); + break; + default: + return complete; } _logger.MethodExit(); @@ -97,7 +110,7 @@ private JobResult PerformManagement(ManagementJobConfiguration config) } catch (GoogleApiException e) { - var googleError = e.Error.ErrorResponseContent; + var googleError = e.Error?.ErrorResponseContent + " " + LogHandler.FlattenException(e); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -114,29 +127,13 @@ private JobResult PerformManagement(ManagementJobConfiguration config) } - private JobResult PerformRemoval(ManagementJobConfiguration config) + private JobResult PerformRemoval(CertificateManagerService svc, ManagementJobConfiguration config, string storePath) { try { _logger.MethodEntry(); - _logger.LogTrace( - $"Credentials JSON: Url: {config.CertificateStoreDetails.ClientMachine} Password: {config.ServerPassword}"); - - var storeProps = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings {DefaultValueHandling = DefaultValueHandling.Populate}); - _logger.LogTrace($"Store Properties: {JsonConvert.SerializeObject(storeProps)}"); - if (storeProps != null) - { - var location = storeProps.Location; - var storePath = $"projects/{config.CertificateStoreDetails.StorePath}/locations/{location}"; - var client = new GcpCertificateManagerClient(); - _logger.LogTrace("Getting Credentials from Google..."); - var svc = client.GetGoogleCredentials(config.CertificateStoreDetails.ClientMachine); - _logger.LogTrace($"Got Credentials from Google"); - - DeleteCertificate(CertificateName, svc, storePath); - } + DeleteCertificate(CertificateName, svc, storePath); _logger.MethodExit(); return new JobResult @@ -148,7 +145,7 @@ private JobResult PerformRemoval(ManagementJobConfiguration config) } catch (GoogleApiException e) { - var googleError = e.Error.ErrorResponseContent; + var googleError = e.Error?.ErrorResponseContent + " " + LogHandler.FlattenException(e); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -163,190 +160,132 @@ private JobResult PerformRemoval(ManagementJobConfiguration config) { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, - FailureMessage = $"PerformRemoval: {LogHandler.FlattenException(e)}" + FailureMessage = $"Management/Remove: {LogHandler.FlattenException(e)}" }; } } - private JobResult PerformAddition(ManagementJobConfiguration config) + private JobResult PerformAddition(CertificateManagerService svc, ManagementJobConfiguration config, string storePath) { //Temporarily only performing additions try { _logger.MethodEntry(); - _logger.LogTrace( - $"Credentials JSON: Url: {config.CertificateStoreDetails.ClientMachine} Password: {config.ServerPassword}"); + var client = new GcpCertificateManagerClient(); - var storeProps = JsonConvert.DeserializeObject(config.CertificateStoreDetails.Properties, - new JsonSerializerSettings {DefaultValueHandling = DefaultValueHandling.Populate}); - _logger.LogTrace($"Store Properties: {JsonConvert.SerializeObject(storeProps)}"); + var duplicate = CheckForDuplicate(storePath, CertificateName, svc); + _logger.LogTrace($"Duplicate? = {duplicate}"); - if (storeProps != null) + //Check for Duplicate already in Google Certificate Manager, if there, make sure the Overwrite flag is checked before replacing + if (duplicate && config.Overwrite || !duplicate) { - var location = storeProps.Location; - var storePath = $"projects/{config.CertificateStoreDetails.StorePath}/locations/{location}"; - var client = new GcpCertificateManagerClient(); - _logger.LogTrace("Getting Credentials from Google..."); - var svc = client.GetGoogleCredentials(config.CertificateStoreDetails.ClientMachine); - _logger.LogTrace($"Got Credentials from Google"); - - var duplicate = CheckForDuplicate(storePath, CertificateName, svc); - _logger.LogTrace($"Duplicate? = {duplicate}"); - - //Check for Duplicate already in Google Certificate Manager, if there, make sure the Overwrite flag is checked before replacing - if (duplicate && config.Overwrite || !duplicate) + _logger.LogTrace("Either not a duplicate or overwrite was chosen...."); + if (!string.IsNullOrWhiteSpace(config.JobCertificate.PrivateKeyPassword)) // This is a PFX Entry { - _logger.LogTrace("Either not a duplicate or overwrite was chosen...."); - if (!string.IsNullOrWhiteSpace(config.JobCertificate.PrivateKeyPassword)) // This is a PFX Entry - { - _logger.LogTrace($"Found Private Key {config.JobCertificate.PrivateKeyPassword}"); - if (string.IsNullOrWhiteSpace(config.JobCertificate.Alias)) - _logger.LogTrace("No Alias Found"); + if (string.IsNullOrWhiteSpace(config.JobCertificate.Alias)) + _logger.LogTrace("No Alias Found"); - // Load PFX - var pfxBytes = Convert.FromBase64String(config.JobCertificate.Contents); - Pkcs12Store p; - using (var pfxBytesMemoryStream = new MemoryStream(pfxBytes)) - { - p = new Pkcs12Store(pfxBytesMemoryStream, - config.JobCertificate.PrivateKeyPassword.ToCharArray()); - } + // Load PFX + var pfxBytes = Convert.FromBase64String(config.JobCertificate.Contents); + Pkcs12Store p; + using (var pfxBytesMemoryStream = new MemoryStream(pfxBytes)) + { + p = new Pkcs12Store(pfxBytesMemoryStream, + config.JobCertificate.PrivateKeyPassword.ToCharArray()); + } - _logger.LogTrace( - $"Created Pkcs12Store containing Alias {config.JobCertificate.Alias} Contains Alias is {p.ContainsAlias(config.JobCertificate.Alias)}"); + _logger.LogTrace( + $"Created Pkcs12Store containing Alias {config.JobCertificate.Alias} Contains Alias is {p.ContainsAlias(config.JobCertificate.Alias)}"); - // Extract private key - string alias; - string privateKeyString; - using (var memoryStream = new MemoryStream()) + // Extract private key + string alias; + string privateKeyString; + using (var memoryStream = new MemoryStream()) + { + using (TextWriter streamWriter = new StreamWriter(memoryStream)) { - using (TextWriter streamWriter = new StreamWriter(memoryStream)) - { - _logger.LogTrace("Extracting Private Key..."); - var pemWriter = new PemWriter(streamWriter); - _logger.LogTrace("Created pemWriter..."); - alias = p.Aliases.Cast().SingleOrDefault(a => p.IsKeyEntry(a)); - _logger.LogTrace($"Alias = {alias}"); - var publicKey = p.GetCertificate(alias).Certificate.GetPublicKey(); - _logger.LogTrace($"publicKey = {publicKey}"); - KeyEntry = p.GetKey(alias); - _logger.LogTrace($"KeyEntry = {KeyEntry}"); - if (KeyEntry == null) throw new Exception("Unable to retrieve private key"); - - var privateKey = KeyEntry.Key; - _logger.LogTrace($"privateKey = {privateKey}"); - var keyPair = new AsymmetricCipherKeyPair(publicKey, privateKey); - - pemWriter.WriteObject(keyPair.Private); - streamWriter.Flush(); - privateKeyString = Encoding.ASCII.GetString(memoryStream.GetBuffer()).Trim() - .Replace("\r", "").Replace("\0", ""); - _logger.LogTrace($"Got Private Key String {privateKeyString}"); - memoryStream.Close(); - streamWriter.Close(); - _logger.LogTrace("Finished Extracting Private Key..."); - } + _logger.LogTrace("Extracting Private Key..."); + var pemWriter = new PemWriter(streamWriter); + _logger.LogTrace("Created pemWriter..."); + alias = p.Aliases.Cast().SingleOrDefault(a => p.IsKeyEntry(a)); + _logger.LogTrace($"Alias = {alias}"); + var publicKey = p.GetCertificate(alias).Certificate.GetPublicKey(); + _logger.LogTrace($"publicKey = {publicKey}"); + KeyEntry = p.GetKey(alias); + _logger.LogTrace($"KeyEntry = {KeyEntry}"); + if (KeyEntry == null) throw new Exception("Unable to retrieve private key"); + + var privateKey = KeyEntry.Key; + var keyPair = new AsymmetricCipherKeyPair(publicKey, privateKey); + + pemWriter.WriteObject(keyPair.Private); + streamWriter.Flush(); + privateKeyString = Encoding.ASCII.GetString(memoryStream.GetBuffer()).Trim() + .Replace("\r", "").Replace("\0", ""); + memoryStream.Close(); + streamWriter.Close(); + _logger.LogTrace("Finished Extracting Private Key..."); } + } - var pubCertPem = - Pemify(Convert.ToBase64String(p.GetCertificate(alias).Certificate.GetEncoded())); - _logger.LogTrace($"Public cert Pem {pubCertPem}"); + var pubCertPem = + Pemify(Convert.ToBase64String(p.GetCertificate(alias).Certificate.GetEncoded())); + _logger.LogTrace($"Public cert Pem {pubCertPem}"); - var certPem = privateKeyString + certStart + pubCertPem + certEnd; + var certPem = privateKeyString + certStart + pubCertPem + certEnd; - _logger.LogTrace($"Got certPem {certPem}"); + _logger.LogTrace($"Got certPem {certPem}"); + pubCertPem = $"-----BEGIN CERTIFICATE-----\r\n{pubCertPem}\r\n-----END CERTIFICATE-----"; - if (MapName.Length > 0 && MapEntryName.Length > 0) - { - var mapCreated = CreateMap(MapName, svc, storePath); - if (mapCreated == null) - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Failure, - JobHistoryId = config.JobHistoryId, - FailureMessage = $"Could not create the certificate map Named: {MapName}" - }; - _logger.LogTrace($"Certificate Map Created with Name {mapCreated.Name}"); - } + _logger.LogTrace($"Public Cert Pem: {pubCertPem}"); - pubCertPem = $"-----BEGIN CERTIFICATE-----\r\n{pubCertPem}\r\n-----END CERTIFICATE-----"; + //Create the certificate in Google + var gCertificate = new Certificate + { + SelfManaged = new SelfManagedCertificate + { PemCertificate = pubCertPem, PemPrivateKey = privateKeyString }, + Name = CertificateName, + Description = CertificateName, + Scope = "DEFAULT" //Scope does not come back in inventory so just hard code it for now + }; - _logger.LogTrace($"Public Cert Pem: {pubCertPem}"); + _logger.LogTrace( + $"Created Google Certificate Object: {JsonConvert.SerializeObject(gCertificate)}"); - //2. Create the certificate in Google - var gCertificate = new Certificate - { - SelfManaged = new SelfManagedCertificate - {PemCertificate = pubCertPem, PemPrivateKey = privateKeyString}, - Name = CertificateName, - Description = CertificateName, - Scope = "DEFAULT" //Scope does not come back in inventory so just hard code it for now - }; - - _logger.LogTrace( - $"Created Google Certificate Object: {JsonConvert.SerializeObject(gCertificate)}"); - - X509Certificate replaceCertificateResponse; - if (duplicate && config.Overwrite) - replaceCertificateResponse = ReplaceCertificate(gCertificate, svc, storePath, true); - else - replaceCertificateResponse = - ReplaceCertificate(gCertificate, svc, storePath, false); - - _logger.LogTrace( - $"Certificate Created with SubjectDn {replaceCertificateResponse.SubjectDN}"); - - if (MapName.Length > 0 && MapEntryName.Length > 0) - { - _logger.LogTrace("Found Map Entry and Map..."); - //Get the host name to be passed into the create map call - var subject = GetCommonNameFromSubject(replaceCertificateResponse.SubjectDN.ToString()); - _logger.LogTrace($"Got Subject: {subject}"); - - var createCertificateMapEntryBody = new CertificateMapEntry - { - Name = MapEntryName, - Description = MapEntryName, - Hostname = subject, - Certificates = new List {$"{storePath}/certificates/{gCertificate.Name}"} - }; - - _logger.LogTrace( - $"Created Certificate Map Entry Body: {JsonConvert.SerializeObject(createCertificateMapEntryBody)}"); - - //4. Check for Existing Map with the same name if matches the Entry Param then use it if not create new - var mapEntryCreated = CreateMapEntry(createCertificateMapEntryBody, svc, - storePath + "/certificateMaps/" + MapName); - _logger.LogTrace($"Certificate Map Entry Created with Name {mapEntryCreated.Name}"); - } + if (duplicate && config.Overwrite) + ReplaceCertificate(gCertificate, svc, storePath); + else + AddCertificate(gCertificate, svc, storePath); - //5. Return success from job - return new JobResult - { - Result = OrchestratorJobStatusJobResult.Success, - JobHistoryId = config.JobHistoryId, - FailureMessage = "" - }; - } + _logger.MethodExit(); + + //Return success from job + return new JobResult + { + Result = OrchestratorJobStatusJobResult.Success, + JobHistoryId = config.JobHistoryId, + FailureMessage = "" + }; } } - _logger.MethodExit(); return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, JobHistoryId = config.JobHistoryId, FailureMessage = - $"Duplicate alias {config.JobCertificate.Alias} found in Google Certificate Manager, to overwrite use the overwrite flag." + $"Duplicate alias {config.JobCertificate.Alias} found in Google Certificate Manager. To overwrite use the overwrite flag." }; } catch (GoogleApiException e) { - var googleError = e.Error.ErrorResponseContent; + var googleError = e.Error?.ErrorResponseContent + " " + LogHandler.FlattenException(e); + _logger.LogError($"PerformManagement Error: {LogHandler.FlattenException(e)}"); + return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -357,6 +296,8 @@ private JobResult PerformAddition(ManagementJobConfiguration config) } catch (Exception e) { + _logger.LogError($"PerformManagement Error: {LogHandler.FlattenException(e)}"); + return new JobResult { Result = OrchestratorJobStatusJobResult.Failure, @@ -366,41 +307,32 @@ private JobResult PerformAddition(ManagementJobConfiguration config) } } - private X509Certificate ReplaceCertificate(Certificate gCertificate, - CertificateManagerService svc, string storePath, bool overwrite) + private void AddCertificate(Certificate gCertificate, CertificateManagerService svc, string storePath) { - try - { - _logger.MethodEntry(); - //DEFAULT or EDGE_CACHE - //todo add labels - //Path does not support cert and private key replacement so delete and insert instead - if (overwrite) DeleteCertificate(gCertificate.Name, svc, storePath); + var addCertificateRequest = svc.Projects.Locations.Certificates.Create(gCertificate, storePath); + addCertificateRequest.CertificateId = gCertificate.Name; - var replaceCertificateRequest = svc.Projects.Locations.Certificates.Create(gCertificate, storePath); - replaceCertificateRequest.CertificateId = gCertificate.Name; - var replaceCertificateResponse = replaceCertificateRequest.Execute(); + var addCertificateResponse = addCertificateRequest.Execute(); + WaitForOperation(svc, addCertificateResponse.Name); - _logger.LogTrace( - $"Certificate Created in Google Cert Manager with Name {replaceCertificateResponse.Name}"); + _logger.LogTrace($"Certificate Created in Google Cert Manager with Name {addCertificateResponse.Name}"); - var pemString = gCertificate.SelfManaged.PemCertificate; - pemString = pemString.Replace("-----BEGIN CERTIFICATE-----", "") - .Replace("-----END CERTIFICATE-----", ""); - var buffer = Convert.FromBase64String(pemString); - var parser = new X509CertificateParser(); - var cert = parser.ReadCertificate(buffer); + _logger.MethodExit(); + } - _logger.LogTrace($"X509 Serialized: {cert}"); + private void ReplaceCertificate(Certificate gCertificate, CertificateManagerService svc, string storePath) + { + _logger.MethodEntry(); - _logger.MethodExit(); - return cert; - } - catch (Exception e) - { - _logger.LogError($"Error occured in Management.CreateCertificate: {LogHandler.FlattenException(e)}"); - throw; - } + var replaceCertificateRequest = svc.Projects.Locations.Certificates.Patch(gCertificate, storePath + $"/certificates/{CertificateName}"); + replaceCertificateRequest.UpdateMask = "SelfManaged"; + + var replaceCertificateResponse = replaceCertificateRequest.Execute(); + WaitForOperation(svc, replaceCertificateResponse.Name); + + _logger.LogTrace($"Certificate Replaced in Google Cert Manager with Name {replaceCertificateResponse.Name}"); + + _logger.MethodExit(); } private void DeleteCertificate(string certificateName, @@ -409,33 +341,6 @@ private void DeleteCertificate(string certificateName, try { _logger.MethodEntry(); - if (MapName.Length > 0) - { - //See if map entry exists, if so delete it - var certificateMapEntryListRequest = - svc.Projects.Locations.CertificateMaps.CertificateMapEntries.List(storePath + - $"/certificateMaps/{MapName}"); - certificateMapEntryListRequest.Filter = - $"name=\"{storePath}/certificateMaps/{MapName}/certificateMapEntries/{MapEntryName}\""; - - var certificateMapEntryListResponse = certificateMapEntryListRequest.Execute(); - _logger.LogTrace( - $"Map Entry Response Json {JsonConvert.SerializeObject(certificateMapEntryListResponse)}"); - - if (certificateMapEntryListResponse?.CertificateMapEntries?.Count > 0) - { - var deleteCertificateMapEntryRequest = - svc.Projects.Locations.CertificateMaps.CertificateMapEntries.Delete(storePath + - $"/certificateMaps/{MapName}/certificateMapEntries/{MapEntryName}"); - - var deleteCertificateMapEntryResponse = deleteCertificateMapEntryRequest.Execute(); - _logger.LogTrace( - $"Delete Certificate Response Json {JsonConvert.SerializeObject(deleteCertificateMapEntryResponse)}"); - - _logger.LogTrace( - $"Deleted {deleteCertificateMapEntryResponse.Name} Certificate Map Entry During Replace Procedure"); - } - } var certificatesRequest = svc.Projects.Locations.Certificates.List(storePath); certificatesRequest.Filter = $"name=\"{storePath}/certificates/{certificateName}\""; @@ -451,118 +356,22 @@ private void DeleteCertificate(string certificateName, var deleteCertificateResponse = deleteCertificateRequest.Execute(); _logger.LogTrace( $"deleteCertificateResponse Json {JsonConvert.SerializeObject(deleteCertificateResponse)}"); + WaitForOperation(svc, deleteCertificateResponse.Name); _logger.LogTrace($"Deleted {deleteCertificateResponse.Name} Certificate During Replace Procedure"); } - - _logger.MethodExit(); - } - catch (Exception e) - { - _logger.LogError($"Error occured in Management.DeleteCertificate: {LogHandler.FlattenException(e)}"); - throw; - } - } - - private CertificateMap CreateMap(string mapName, CertificateManagerService client, string parent) - { - try - { - _logger.MethodEntry(); - var certificateMapListRequest = client.Projects.Locations.CertificateMaps.List(parent); - var mapFilter = $"{parent}/certificateMaps/{mapName}"; - certificateMapListRequest.Filter = $"name=\"{mapFilter}\""; - - var certificateMapListResponse = certificateMapListRequest.Execute(); - _logger.LogTrace( - $"certificateMapListResponse Json {JsonConvert.SerializeObject(certificateMapListResponse)}"); - - if (certificateMapListResponse?.CertificateMaps?.Count > 0) - { - _logger.MethodExit(); - return certificateMapListResponse.CertificateMaps[0]; - } - - var certificateMapBody = new CertificateMap {Name = mapName, Description = mapName}; - var certificateMapCreateRequest = - client.Projects.Locations.CertificateMaps.Create(certificateMapBody, parent); - certificateMapCreateRequest.CertificateMapId = mapName; - - var certificateMapCreateResponse = certificateMapCreateRequest.Execute(); - _logger.LogTrace( - $"certificateMapCreateResponse Json {JsonConvert.SerializeObject(certificateMapCreateResponse)}"); - - if (certificateMapCreateResponse?.Name?.Length > 0) - { - var certificateMapRequest = - client.Projects.Locations.CertificateMaps.Get(mapFilter); - - var certificateMapResponse = certificateMapRequest.Execute(); - _logger.LogTrace( - $"certificateMapResponse Json {JsonConvert.SerializeObject(certificateMapResponse)}"); - - _logger.MethodExit(); - return certificateMapResponse; - } - - _logger.MethodExit(); - return null; - } - catch (Exception e) - { - _logger.LogError($"Error occured in Management.CreateMap: {LogHandler.FlattenException(e)}"); - throw; - } - } - - private CertificateMapEntry CreateMapEntry(CertificateMapEntry mapEntry, CertificateManagerService client, - string parent) - { - try - { - _logger.MethodEntry(); - var certificateMapEntryListRequest = - client.Projects.Locations.CertificateMaps.CertificateMapEntries.List(parent); - certificateMapEntryListRequest.Filter = $"name=\"{mapEntry.Name}\""; - - var certificateMapEntryListResponse = certificateMapEntryListRequest.Execute(); - _logger.LogTrace( - $"certificateMapEntryListResponse Json {JsonConvert.SerializeObject(certificateMapEntryListResponse)}"); - - if (certificateMapEntryListResponse?.CertificateMapEntries?.Count > 0) - { - _logger.MethodExit(); - return certificateMapEntryListResponse.CertificateMapEntries[0]; - } - - var certificateMapEntryCreateRequest = - client.Projects.Locations.CertificateMaps.CertificateMapEntries.Create(mapEntry, parent); - certificateMapEntryCreateRequest.CertificateMapEntryId = mapEntry.Name; - - var certificateMapEntryCreateResponse = certificateMapEntryCreateRequest.Execute(); - _logger.LogTrace( - $"certificateMapEntryCreateResponse Json {JsonConvert.SerializeObject(certificateMapEntryCreateResponse)}"); - - if (certificateMapEntryCreateResponse?.Name?.Length > 0) + else { - var certificateMapEntryRequest = - client.Projects.Locations.CertificateMaps.CertificateMapEntries.Get(parent + - "/certificateMapEntries/" + mapEntry.Name); - - var certificateMapEntryResponse = certificateMapEntryRequest.Execute(); - _logger.LogTrace( - $"certificateMapEntryResponse Json {JsonConvert.SerializeObject(certificateMapEntryResponse)}"); - - _logger.MethodExit(); - return certificateMapEntryResponse; + string msg = $"Certificate {certificateName} not found for {storePath}."; + _logger.LogWarning(msg); + throw new Exception(msg); } _logger.MethodExit(); - return null; } catch (Exception e) { - _logger.LogError($"Error occured in Management.CreateMapEntry: {LogHandler.FlattenException(e)}"); + _logger.LogError($"Error occured in Management.DeleteCertificate: {LogHandler.FlattenException(e)}"); throw; } } @@ -596,29 +405,31 @@ private bool CheckForDuplicate(string path, string alias, CertificateManagerServ } } - private string GetMapSettingsFromAlias(string alias, string nameType) + private void WaitForOperation(CertificateManagerService client, string operationName) { - try - { - _logger.MethodEntry(); - //alias should be in format MapName/MapEntryName/CertificateName - _logger.LogTrace($"nameType: {nameType} alias: {alias}"); - var aliasComponents = alias.Split('/'); - if (aliasComponents.Length == 3 && nameType == "map") return aliasComponents[0].ToLower(); - if (aliasComponents.Length == 3 && nameType == "mapentry") return aliasComponents[1].ToLower(); - if (aliasComponents.Length == 3 && nameType == "certificate") return aliasComponents[2].ToLower(); - if (aliasComponents.Length == 1 && nameType == "certificate" && aliasComponents[0].Length > 0) - return aliasComponents[0].ToLower(); + _logger.MethodEntry(); - _logger.MethodExit(); - return ""; - } - catch (Exception e) + DateTime endTime = DateTime.Now.AddMilliseconds(OPERATION_MAX_WAIT_MILLISECONDS); + Operation operation = new Operation(); + ProjectsResource.LocationsResource.OperationsResource.GetRequest getRequest = client.Projects.Locations.Operations.Get(operationName); + + while (DateTime.Now < endTime) { - _logger.LogError( - $"Error in Management.GetMapSettingsFromAlias {LogHandler.FlattenException(e)}"); - throw; + _logger.LogTrace($"Attempting WAIT for {operationName} at {DateTime.Now.ToString()}."); + operation = getRequest.Execute(); + + if (operation.Done == true) + { + _logger.LogDebug($"End WAIT for {operationName}. Task DONE."); + _logger.MethodExit(); + return; + } + + System.Threading.Thread.Sleep(OPERATION_INTERVAL_WAIT_MILLISECONDS); } + + _logger.MethodExit(); + throw new Exception($"{operationName} was still processing after the {OPERATION_MAX_WAIT_MILLISECONDS.ToString()} millisecond maximum wait time."); } private string GetCommonNameFromSubject(string subject) diff --git a/GcpCertManager/StorePath.cs b/GcpCertManager/StoreProperties.cs similarity index 58% rename from GcpCertManager/StorePath.cs rename to GcpCertManager/StoreProperties.cs index 8e7e023..d905a53 100644 --- a/GcpCertManager/StorePath.cs +++ b/GcpCertManager/StoreProperties.cs @@ -3,12 +3,13 @@ namespace Keyfactor.Extensions.Orchestrator.GcpCertManager { - internal class StorePath + internal class StoreProperties { - [JsonProperty("Location")] [DefaultValue("global")] public string Location { get; set; } - [JsonProperty("Project Number")] public string ProjectNumber { get; set; } + public string ProjectId { get; set; } + + public string ServiceAccountKey { get; set; } } } \ No newline at end of file diff --git a/GcpCertManager/manifest.json b/GcpCertManager/manifest.json index 9321730..e728639 100644 --- a/GcpCertManager/manifest.json +++ b/GcpCertManager/manifest.json @@ -1,11 +1,11 @@ { "extensions": { "Keyfactor.Orchestrators.Extensions.IOrchestratorJobExtension": { - "CertStores.GcpCertManager.Inventory": { + "CertStores.GcpCertMgr.Inventory": { "assemblypath": "GcpCertManager.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.GcpCertManager.Jobs.Inventory" }, - "CertStores.GcpCertManager.Management": { + "CertStores.GcpCertMgr.Management": { "assemblypath": "GcpCertManager.dll", "TypeFullName": "Keyfactor.Extensions.Orchestrator.GcpCertManager.Jobs.Management" } diff --git a/GcpCertManagerTestConsole/GcpCertManagerTestConsole.csproj b/GcpCertManagerTestConsole/GcpCertManagerTestConsole.csproj deleted file mode 100644 index bef3be2..0000000 --- a/GcpCertManagerTestConsole/GcpCertManagerTestConsole.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Exe - netcoreapp3.1 - - - - - - - - - - - - - - - diff --git a/GcpCertManagerTestConsole/Program.cs b/GcpCertManagerTestConsole/Program.cs deleted file mode 100644 index 022dd8a..0000000 --- a/GcpCertManagerTestConsole/Program.cs +++ /dev/null @@ -1,122 +0,0 @@ -using System; -using System.Collections.Generic; -using Keyfactor.Extensions.Orchestrator.GcpCertManager.Jobs; -using Keyfactor.Orchestrators.Common.Enums; -using Keyfactor.Orchestrators.Extensions; -using Microsoft.Extensions.Logging; -using Newtonsoft.Json; - -namespace GcpCertManagerTestConsole -{ - internal class Program - { - private static void Main(string[] args) - { - if (args.Length != 0) return; - // Display message to user to provide parameters. - Console.WriteLine("Please enter parameter values. Inventory or Management"); - var input = Console.ReadLine(); - Console.WriteLine("Enter Client Machine Json File Name freshcertman-9ff09dc4ff67.json"); - var clientMachine = Console.ReadLine(); - Console.WriteLine("Enter Google Cloud Store Path freshcertman"); - var storePath = Console.ReadLine(); - - switch (input) - { - case "Inventory": - ILoggerFactory invLoggerFactory = new LoggerFactory(); - var invLogger = invLoggerFactory.CreateLogger(); - - var inv = new Inventory(invLogger); - - var invJobConfig = GetInventoryJobConfiguration(clientMachine, storePath); - - SubmitInventoryUpdate sui = GetItems; - inv.ProcessJob(invJobConfig, sui); - Console.Write("Successful Inventory!"); - break; - case "Management": - Console.WriteLine("Select Management Type Add or Remove"); - var mgmtType = Console.ReadLine(); - Console.WriteLine("Overwrite? Enter true or false"); - var overWrite = Console.ReadLine(); - Console.WriteLine("Alias Enter Alias Name"); - var alias = Console.ReadLine(); - Console.WriteLine("Enter Location global or another region"); - var location = Console.ReadLine(); - Console.WriteLine("Enter Google Cloud Project Number 210777491775"); - var projectNumber = Console.ReadLine(); - - if (mgmtType == "Add") - { - ILoggerFactory loggerFactory = new LoggerFactory(); - var logger = loggerFactory.CreateLogger(); - - var mgmt = new Management(logger); - - var jobConfiguration = GetJobConfiguration(clientMachine, storePath, projectNumber, - overWrite, - alias, location); - - var result = mgmt.ProcessJob(jobConfiguration); - - if (result.Result == OrchestratorJobStatusJobResult.Success) Console.Write("Success"); - } - - if (mgmtType == "Remove") - { - ILoggerFactory loggerFactory = new LoggerFactory(); - var logger = loggerFactory.CreateLogger(); - - var mgmt = new Management(logger); - var jobConfig = GetRemoveJobConfiguration(clientMachine, storePath, projectNumber, - overWrite, alias, location); - var result = mgmt.ProcessJob(jobConfig); - - if (result.Result == OrchestratorJobStatusJobResult.Success) Console.Write("Success"); - } - - break; - } - } - - - public static bool GetItems(IEnumerable items) - { - return true; - } - - public static ManagementJobConfiguration GetJobConfiguration(string clientMachineJson, string storePath, - string projectNumber, string overWrite, - string alias, string location) - { - var privateKeyConfig = - $"{{\"LastInventory\":[],\"CertificateStoreDetails\":{{\"ClientMachine\":\"{clientMachineJson}\",\"StorePath\":\"{storePath}\",\"StorePassword\":null,\"Properties\":\"{{\\\"Location\\\":\\\"{location}\\\",\\\"Project Number\\\":\\\"{projectNumber}\\\"}}\",\"Type\":6110}},\"OperationType\":2,\"Overwrite\":{overWrite},\"JobCertificate\":{{\"Thumbprint\":null,\"Contents\":\"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\",\"Alias\":\"{alias}\",\"PrivateKeyPassword\":\"zsvkW2cyfc4w\"}},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":362893,\"RequestStatus\":1,\"ServerUsername\":null,\"ServerPassword\":null,\"UseSSL\":true,\"JobProperties\":{{}},\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"6f4f5268-bf51-4ca0-870b-94c3c8a82fe3\",\"Capability\":\"CertStores.GcpCertManager.Management\"}}"; - - var jobConfigString = privateKeyConfig; - - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } - - public static InventoryJobConfiguration GetInventoryJobConfiguration(string clientMachineJson, string storePath) - { - var jobConfigString = - "{\"LastInventory\":[{\"Alias\":\"cert12\",\"PrivateKeyEntry\":true,\"Thumbprints\":[\"8C70C6EFBDDB09627DFECC6761B46CC6EF0C578D\"]},{\"Alias\":\"cert20\",\"PrivateKeyEntry\":true,\"Thumbprints\":[\"C97D508D9192872D7349A79F7AEE13201443F844\"]},{\"Alias\":\"cert27\",\"PrivateKeyEntry\":true,\"Thumbprints\":[\"7FE8827A8626307BD635D50EF8EE2A64D91DF9F7\"]},{\"Alias\":\"map30/me30/cert30\",\"PrivateKeyEntry\":true,\"Thumbprints\":[\"480E773F01F188E3731D6E921ED83E53C0D1F08D\"]}],\"CertificateStoreDetails\":{\"ClientMachine\":\"" + - clientMachineJson + "\",\"StorePath\":\"" + storePath + - "\",\"StorePassword\":\"\",\"Properties\":\"{\\\"Location\\\":\\\"global\\\",\\\"Project Number\\\":\\\"210777491775\\\"}\",\"Type\":6110},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":362890,\"RequestStatus\":1,\"ServerUsername\":null,\"ServerPassword\":null,\"UseSSL\":true,\"JobProperties\":null,\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"e131894b-1bbf-4743-8a01-534dc89d409b\",\"Capability\":\"CertStores.GcpCertManager.Inventory\"}"; - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } - - public static ManagementJobConfiguration GetRemoveJobConfiguration(string clientMachineJson, string storePath, - string projectNumber, string overWrite, - string alias, string location) - { - var jobConfigString = - $"{{\"LastInventory\":[],\"CertificateStoreDetails\":{{\"ClientMachine\":\"{clientMachineJson}\",\"StorePath\":\"{storePath}\",\"StorePassword\":null,\"Properties\":\"{{\\\"Location\\\":\\\"{location}\\\",\\\"Project Number\\\":\\\"{projectNumber}\\\"}}\",\"Type\":6110}},\"OperationType\":3,\"Overwrite\":{overWrite},\"JobCertificate\":{{\"Thumbprint\":null,\"Contents\":\"\",\"Alias\":\"{alias}\",\"PrivateKeyPassword\":null}},\"JobCancelled\":false,\"ServerError\":null,\"JobHistoryId\":362897,\"RequestStatus\":1,\"ServerUsername\":null,\"ServerPassword\":null,\"UseSSL\":true,\"JobProperties\":{{}},\"JobTypeId\":\"00000000-0000-0000-0000-000000000000\",\"JobId\":\"bcc9031a-8c37-4def-8de9-3bafe4afd0c1\",\"Capability\":\"CertStores.GcpCertManager.Management\"}}"; - var result = JsonConvert.DeserializeObject(jobConfigString); - return result; - } - } -} \ No newline at end of file diff --git a/README.md b/README.md index bfd7362..436f000 100644 --- a/README.md +++ b/README.md @@ -1,43 +1,78 @@ + # Google Cloud Provider Certificate Manager Google Certificate Manager Orchestrator for Add, Remove and Inventory. -#### Integration status: Pilot - Ready for use in test environments. Not for use in production. +#### Integration status: Production - Ready for use in production environments. + +## About the Keyfactor Universal Orchestrator Extension + +This repository contains a Universal Orchestrator Extension which is a plugin to the Keyfactor Universal Orchestrator. Within the Keyfactor Platform, Orchestrators are used to manage “certificate stores” — collections of certificates and roots of trust that are found within and used by various applications. + +The Universal Orchestrator is part of the Keyfactor software distribution and is available via the Keyfactor customer portal. For general instructions on installing Extensions, see the “Keyfactor Command Orchestrator Installation and Configuration Guide” section of the Keyfactor documentation. For configuration details of this specific Extension see below in this readme. + +The Universal Orchestrator is the successor to the Windows Orchestrator. This Orchestrator Extension plugin only works with the Universal Orchestrator and does not work with the Windows Orchestrator. + +## Support for Google Cloud Provider Certificate Manager -## About the Keyfactor Universal Orchestrator Capability +Google Cloud Provider Certificate Manager is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com -This repository contains a Universal Orchestrator Capability which is a plugin to the Keyfactor Universal Orchestrator. Within the Keyfactor Platform, Orchestrators are used to manage “certificate stores” — collections of certificates and roots of trust that are found within and used by various applications. +###### 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. -The Universal Orchestrator is part of the Keyfactor software distribution and is available via the Keyfactor customer portal. For general instructions on installing Capabilities, see the “Keyfactor Command Orchestrator Installation and Configuration Guide” section of the Keyfactor documentation. For configuration details of this specific Capability, see below in this readme. +--- -The Universal Orchestrator is the successor to the Windows Orchestrator. This Capability plugin only works with the Universal Orchestrator and does not work with the Windows Orchestrator. --- +## Keyfactor Version Supported + +The minimum version of the Keyfactor Universal Orchestrator Framework needed to run this version of the extension is 10.4.1 +## Platform Specific Notes + +The Keyfactor Universal Orchestrator may be installed on either Windows or Linux based platforms. The certificate operations supported by a capability may vary based what platform the capability is installed on. The table below indicates what capabilities are supported based on which platform the encompassing Universal Orchestrator is running. +| Operation | Win | Linux | +|-----|-----|------| +|Supports Management Add|✓ |✓ | +|Supports Management Remove|✓ |✓ | +|Supports Create Store| | | +|Supports Discovery| | | +|Supports Reenrollment| | | +|Supports Inventory|✓ |✓ | + + + + --- -**Google Cloud Platform Certificate Manager** + +## Google Cloud Platform Certificate Manager **Overview** -The GCP Certificate Manager Orchestrator remotely manages certificates on the Google Cloud Platform Certificate Manager Product +The GCP Certificate Manager Orchestrator Extension remotely manages certificates on the Google Cloud Platform Certificate Manager Product -This agent implements three job types – Inventory, Management Add, and Management Remove. Below are the steps necessary to configure this AnyAgent. It supports adding certificates with or without private keys. +This orchestrator extension implements three job types – Inventory, Management Add, and Management Remove. Below are the steps necessary to configure this Orchestrator Extension. It supports adding certificates with private keys only. The GCP Certificate Manager Orchestrator Extension supports the replacement of unbound certificates as well as certificates bound to existing map entries, but it does **not** support specifying map entry bindings when adding new certificates. **Google Cloud Configuration** 1. Read up on [Google Certificate Manager](https://cloud.google.com/certificate-manager/docs) and how it works. -2. A Google Service Account is needed with the following permissions (Note: Workload Identity Management Should be used but at the time of the writing it was not available in the .net library yet) + +2. Either a Google Service Account is needed with the following permissions (Note: Workload Identity Management Should be used but at the time of the writing it was not available in the .net library yet), or the virtual machine running the Keyfactor Orchestrator Service must reside within Google Cloud. ![](images/ServiceAccountSettings.gif) + 3. The following Api Access is needed: ![](images/ApiAccessNeeded.gif) -4. Dowload the Json Credential file as shown below: + +4. If authenticating via service account, download the Json Credential file as shown below: ![](images/GoogleKeyJsonDownload.gif) + +## Keyfactor Command Configuration + **1. Create the New Certificate Store Type for the GCP Certificate Manager Orchestrator** In Keyfactor Command create a new Certificate Store Type similar to the one below: @@ -45,20 +80,21 @@ In Keyfactor Command create a new Certificate Store Type similar to the one belo #### STORE TYPE CONFIGURATION SETTING TAB | CONFIG ELEMENT | DESCRIPTION ------|-----------|------------------ -Basic |Name |Descriptive name for the Store Type. Google Cloud Certificate Manager can be used. -Basic |Short Name |The short name that identifies the registered functionality of the orchestrator. Must be GcpCertMgr -Basic |Custom Capability|Checked with Name GcpCertManager -Basic |Job Types |Inventory, Add, and Remove are the supported job types. -Basic |Needs Server |Must be checked +Basic |Name |Descriptive name for the Store Type. Example: Google Cloud Certificate Manager +Basic |Short Name |The name that identifies the registered functionality of the orchestrator. Must be GcpCertMgr +Basic |Custom Capability|Unchecked +Basic |Job Types |Inventory, Add, and Remove are the supported job types +Basic |Needs Server |Unchecked Basic |Blueprint Allowed |Unchecked -Basic |Requires Store Password |Determines if a store password is required when configuring an individual store. This must be unchecked. -Basic |Supports Entry Password |Determined if an individual entry within a store can have a password. This must be unchecked. -Advanced |Store Path Type| Determines how the user will enter the store path when setting up the cert store. Freeform -Advanced |Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Required -Advanced |Private Key Handling |Determines how the orchestrator deals with private keys. Required -Advanced |PFX Password Style |Determines password style for the PFX Password. Default -Custom Fields|Google Cloud Platform Project Location|Name:Location Display Name:Location Type:String Default Value:global Required:True -Custom Fields|Google Cloud Platform Project Number|Name:Project Number Display Name:Project Number Type:String Default Value:N/A Required:True +Basic |Requires Store Password |Unchecked +Basic |Supports Entry Password |Unchecked +Advanced |Store Path Type| Fixed +Advanced |Store Path Type Value (the textbox that appears below Store Path Type when Store Path Type is set to "Fixed") |n/a +Advanced |Supports Custom Alias |Required +Advanced |Private Key Handling |Required +Advanced |PFX Password Style |Default +Custom Fields|Google Cloud Platform Project Location/Region|Name:Location, Display Name:Location, Type:String, Default Value:global, Required:False +Custom Fields|The file name of the Google Cloud Service Account Key File installed in the same folder as the orchestrator extension. Empty if the orchestrator server resides in GCP and you are not using a service account key |Name:Service Account Key File Name, Type:String, Default Value: (leave blank), Required:True Entry Parameters|N/A| There are no Entry Parameters **Basic Settings:** @@ -72,6 +108,8 @@ Entry Parameters|N/A| There are no Entry Parameters **Custom Fields:** ![](images/CertStoreType-CustomFields.gif) +![](images/CertStoreType-CustomField-Location.gif) +![](images/CertStoreType-CustomField-ServiceAccountKey.gif) **Entry Params:** @@ -83,88 +121,25 @@ See Keyfactor InstallingKeyfactorOrchestrators.pdf Documentation. Get from your **3. Create a GCP Certificate Manager Certificate Store within Keyfactor Command** In Keyfactor Command create a new Certificate Store similar to the one below -![](images/CertStoreSettings-1.gif) -![](images/CertStoreSettings-2.gif) -![](images/GoogleCloudProjectInfo.gif) +![](images/CertStoreSettings.gif) #### STORE CONFIGURATION CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category |The type of certificate store to be configured. Select category based on the display name configured above "GCP Certificate Manager". Container |This is a logical grouping of like stores. This configuration is optional and does not impact the functionality of the store. -Client Machine |The name of the Google Certificate Manager Credentials File. This file should be stored in the same directory as the Orchestrator binary. Sample is "favorable-tree-346417-feb22d67de35.json". -Store Path |This will be the ProjectId of the Google Cloud Project. Sample here is "favorable-tree-346417". See above image. -Location|global is the default but could be another region based on the project. -Project Number| As shown in the above image, this can be obtained from the project information in Google Cloud. +Client Machine |Your GCP Project ID for your account. +Store Path |This is not used and should be defaulted to n/a per the certificate store type set up. Orchestrator |This is the orchestrator server registered with the appropriate capabilities to manage this certificate store type. -Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. +Location|**global** is the default but could be another region based on the project. +Service Account Key File Name | The name of the file containing the GCP Service Account JSON formatted key previously downloaded. Keep this optional field blank if the Keyfactor Orchestrator Service is running from an authenticated VM within Google Cloud. +Update Server User Name |Click and select No Value. +Update Server Password |Click and select No Value. Use SSL |This should be checked. -User |This is not necessary. -Password |This is not necessary. - -*** - -#### Usage - -**Adding New Certificate No Map Entry** - -![](images/AddCertificateNoMapEntry.gif) - -*** - -**Adding New Certificate With Map Entry** - -![](images/AddCertificateWithMapEntry.gif) - -*** - -**Replace Certficate With Map Entry** - -![](images/ReplaceCertificateMapEntry.gif) - -*** - -**Replace Certficate No Map Entry** - -![](images/ReplaceCertificateNoMapEntry.gif) - -*** - -**Replace Certficate With Map Entry** - -![](images/ReplaceCertificateMapEntry.gif) - -*** - -**Replace Certficate No Map Entry** - -![](images/ReplaceCertificateNoMapEntry.gif) - -*** - -**Remove Certificate Map Entry** - -![](images/RemoveCertifcateMapEntry.gif) - -*** - -**Remove Certficate No Map Entry** +Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. -![](images/RemoveCertificateNoMapEntry.gif) -#### TEST CASES -Case Number|Case Name|Case Description|Overwrite Flag|Alias Name|Expected Results|Passed -------------|---------|----------------|--------------|----------|----------------|-------------- -1|Fresh Add with New Map and Entry|Will create new map, map entry and cert|False|map12/mentry12/cert12|New Map will be created, New Map Entry Created, New Cert Created|True -1a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|map12/mentry12/cert12|Error Occurs Saying to Use Overwrite Flag|True -1b|Try Replace with Overwrite|Should delete and re-insert mapentry and certificate|True|map12/mentry12/cert12|Replaced Cert Map Entry and Certificate|True -2|Fresh Add with Cert Only (No Map)|Will create cert that is not tied to map|False|cert40|Created Certificate with alias cert40|True -2a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|Cert40|Error Occurs Saying to Use Overwrite Flag|True -2b|Try Replace with Overwrite|If user uses overwrite will replace cert|True|cert40|Certificate with be replaced with alias of cert40|True -3|Fresh Add with new entry to existing map|Will create cert where entry is tied to an existing map|False|map12/mentry50/cert50|Created Certificate with alias map12/mentry50/cert50|True -3a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|map12/mentry50/cert50|Error Occurs Saying to Use Overwrite Flag|True -4|Remove Cert In Map|Try to remove cert in existing map. Should leave map and delete cert map entry and cert.|N/A|map12/mentry50/cert50|Cert cert50 and map entry mentry50 should be deleted.|True -4a|Remove Standalone cert (No Map)|Try to remove cert without a map entry or map.|N/A|cert40|Cert cert40 should be deleted.|True +When creating cert store type manually, that store property names and entry parameter names are case sensitive diff --git a/images/AddCertificateNoMapEntry.gif b/images/AddCertificateNoMapEntry.gif deleted file mode 100644 index d6c4c96..0000000 Binary files a/images/AddCertificateNoMapEntry.gif and /dev/null differ diff --git a/images/AddCertificateWithMapEntry.gif b/images/AddCertificateWithMapEntry.gif deleted file mode 100644 index fe57970..0000000 Binary files a/images/AddCertificateWithMapEntry.gif and /dev/null differ diff --git a/images/CertStoreSettings-1.gif b/images/CertStoreSettings-1.gif deleted file mode 100644 index 441d2f4..0000000 Binary files a/images/CertStoreSettings-1.gif and /dev/null differ diff --git a/images/CertStoreSettings-2.gif b/images/CertStoreSettings-2.gif deleted file mode 100644 index eac6177..0000000 Binary files a/images/CertStoreSettings-2.gif and /dev/null differ diff --git a/images/CertStoreSettings.gif b/images/CertStoreSettings.gif new file mode 100644 index 0000000..7347fb8 Binary files /dev/null and b/images/CertStoreSettings.gif differ diff --git a/images/CertStoreType-Advanced.gif b/images/CertStoreType-Advanced.gif index 402feb4..9e1f062 100644 Binary files a/images/CertStoreType-Advanced.gif and b/images/CertStoreType-Advanced.gif differ diff --git a/images/CertStoreType-Basic.gif b/images/CertStoreType-Basic.gif index 03e4377..227fd38 100644 Binary files a/images/CertStoreType-Basic.gif and b/images/CertStoreType-Basic.gif differ diff --git a/images/CertStoreType-CustomField-Location.gif b/images/CertStoreType-CustomField-Location.gif new file mode 100644 index 0000000..136eb2f Binary files /dev/null and b/images/CertStoreType-CustomField-Location.gif differ diff --git a/images/CertStoreType-CustomField-ServiceAccountKey.gif b/images/CertStoreType-CustomField-ServiceAccountKey.gif new file mode 100644 index 0000000..617d934 Binary files /dev/null and b/images/CertStoreType-CustomField-ServiceAccountKey.gif differ diff --git a/images/CertStoreType-CustomFields.gif b/images/CertStoreType-CustomFields.gif index 1b8abad..9aa24fa 100644 Binary files a/images/CertStoreType-CustomFields.gif and b/images/CertStoreType-CustomFields.gif differ diff --git a/images/GoogleCloudProjectInfo.gif b/images/GoogleCloudProjectInfo.gif deleted file mode 100644 index f0ac697..0000000 Binary files a/images/GoogleCloudProjectInfo.gif and /dev/null differ diff --git a/images/RemoveCertifcateMapEntry.gif b/images/RemoveCertifcateMapEntry.gif deleted file mode 100644 index 7f99d85..0000000 Binary files a/images/RemoveCertifcateMapEntry.gif and /dev/null differ diff --git a/images/RemoveCertificateNoMapEntry.gif b/images/RemoveCertificateNoMapEntry.gif deleted file mode 100644 index a041836..0000000 Binary files a/images/RemoveCertificateNoMapEntry.gif and /dev/null differ diff --git a/images/ReplaceCertificateMapEntry.gif b/images/ReplaceCertificateMapEntry.gif deleted file mode 100644 index 225dcb7..0000000 Binary files a/images/ReplaceCertificateMapEntry.gif and /dev/null differ diff --git a/images/ReplaceCertificateNoMapEntry.gif b/images/ReplaceCertificateNoMapEntry.gif deleted file mode 100644 index d45d361..0000000 Binary files a/images/ReplaceCertificateNoMapEntry.gif and /dev/null differ diff --git a/integration-manifest.json b/integration-manifest.json index a94e7c9..a3ea0b9 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -1,9 +1,81 @@ { - "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", - "integration_type": "orchestrator", - "name": "GCP Certificate Manager", - "update_catalog": true, - "link_github": false, - "status": "pilot", - "description": "GCP Certificate Manager Orchestrator for Add, Remove and Inventory." + "$schema": "https://keyfactor.github.io/integration-manifest-schema.json", + "integration_type": "orchestrator", + "name": "Google Cloud Provider Certificate Manager", + "status": "production", + "update_catalog": false, + "support_level": "kf-supported", + "link_github": true, + "release_dir": "GcpCertManager/bin/release", + "description": "Google Certificate Manager Orchestrator for Add, Remove and Inventory.", + "about": { + "orchestrator": { + "UOFramework": "10.4.1", + "keyfactor_platform_version": "9.1.0", + "pam_support": false, + "win": { + "supportsCreateStore": false, + "supportsDiscovery": false, + "supportsManagementAdd": true, + "supportsManagementRemove": true, + "supportsReenrollment": false, + "supportsInventory": true, + "platformSupport": "Unused" + }, + "linux": { + "supportsCreateStore": false, + "supportsDiscovery": false, + "supportsManagementAdd": true, + "supportsManagementRemove": true, + "supportsReenrollment": false, + "supportsInventory": true, + "platformSupport": "Unused" + }, + "store_types": { + "GcpCertMgr": { + "Name": "GCP Certificate Manager", + "ShortName": "GcpCertMgr", + "Capability": "GcpCertMgr", + "ServerRequired": false, + "BlueprintAllowed": false, + "CustomAliasAllowed": "Required", + "PowerShell": false, + "PrivateKeyAllowed": "Required", + "StorePathType": "", + "StorePathValue": "n/a", + "SupportedOperations": { + "Add": true, + "Create": true, + "Discovery": true, + "Enrollment": false, + "Remove": true + }, + "PasswordOptions": { + "Style": "Default", + "EntrySupported": false, + "StoreRequired": false + }, + "Properties": [ + { + "Name": "Location", + "DisplayName": "Location", + "Type": "String", + "DependsOn": "", + "DefaultValue": "global", + "Required": true + }, + { + "Name": "ServiceAccountKey", + "DisplayName": "Service Account Key File Path", + "Type": "String", + "DependsOn": "", + "DefaultValue": null, + "Required": false + } + ], + "EntryParameters": [] + } + } + } + } } diff --git a/readme_source.md b/readme_source.md index b967f18..b0d5003 100644 --- a/readme_source.md +++ b/readme_source.md @@ -1,22 +1,28 @@ -**Google Cloud Platform Certificate Manager** +## Google Cloud Platform Certificate Manager **Overview** -The GCP Certificate Manager Orchestrator remotely manages certificates on the Google Cloud Platform Certificate Manager Product +The GCP Certificate Manager Orchestrator Extension remotely manages certificates on the Google Cloud Platform Certificate Manager Product -This agent implements three job types – Inventory, Management Add, and Management Remove. Below are the steps necessary to configure this AnyAgent. It supports adding certificates with or without private keys. +This orchestrator extension implements three job types – Inventory, Management Add, and Management Remove. Below are the steps necessary to configure this Orchestrator Extension. It supports adding certificates with private keys only. The GCP Certificate Manager Orchestrator Extension supports the replacement of unbound certificates as well as certificates bound to existing map entries, but it does **not** support specifying map entry bindings when adding new certificates. **Google Cloud Configuration** 1. Read up on [Google Certificate Manager](https://cloud.google.com/certificate-manager/docs) and how it works. -2. A Google Service Account is needed with the following permissions (Note: Workload Identity Management Should be used but at the time of the writing it was not available in the .net library yet) + +2. Either a Google Service Account is needed with the following permissions (Note: Workload Identity Management Should be used but at the time of the writing it was not available in the .net library yet), or the virtual machine running the Keyfactor Orchestrator Service must reside within Google Cloud. ![](images/ServiceAccountSettings.gif) + 3. The following Api Access is needed: ![](images/ApiAccessNeeded.gif) -4. Dowload the Json Credential file as shown below: + +4. If authenticating via service account, download the Json Credential file as shown below: ![](images/GoogleKeyJsonDownload.gif) + +## Keyfactor Command Configuration + **1. Create the New Certificate Store Type for the GCP Certificate Manager Orchestrator** In Keyfactor Command create a new Certificate Store Type similar to the one below: @@ -24,20 +30,21 @@ In Keyfactor Command create a new Certificate Store Type similar to the one belo #### STORE TYPE CONFIGURATION SETTING TAB | CONFIG ELEMENT | DESCRIPTION ------|-----------|------------------ -Basic |Name |Descriptive name for the Store Type. Google Cloud Certificate Manager can be used. -Basic |Short Name |The short name that identifies the registered functionality of the orchestrator. Must be GcpCertMgr -Basic |Custom Capability|Checked with Name GcpCertManager -Basic |Job Types |Inventory, Add, and Remove are the supported job types. -Basic |Needs Server |Must be checked +Basic |Name |Descriptive name for the Store Type. Example: Google Cloud Certificate Manager +Basic |Short Name |The name that identifies the registered functionality of the orchestrator. Must be GcpCertMgr +Basic |Custom Capability|Unchecked +Basic |Job Types |Inventory, Add, and Remove are the supported job types +Basic |Needs Server |Unchecked Basic |Blueprint Allowed |Unchecked -Basic |Requires Store Password |Determines if a store password is required when configuring an individual store. This must be unchecked. -Basic |Supports Entry Password |Determined if an individual entry within a store can have a password. This must be unchecked. -Advanced |Store Path Type| Determines how the user will enter the store path when setting up the cert store. Freeform -Advanced |Supports Custom Alias |Determines if an individual entry within a store can have a custom Alias. This must be Required -Advanced |Private Key Handling |Determines how the orchestrator deals with private keys. Required -Advanced |PFX Password Style |Determines password style for the PFX Password. Default -Custom Fields|Google Cloud Platform Project Location|Name:Location Display Name:Location Type:String Default Value:global Required:True -Custom Fields|Google Cloud Platform Project Number|Name:Project Number Display Name:Project Number Type:String Default Value:N/A Required:True +Basic |Requires Store Password |Unchecked +Basic |Supports Entry Password |Unchecked +Advanced |Store Path Type| Fixed +Advanced |Store Path Type Value (the textbox that appears below Store Path Type when Store Path Type is set to "Fixed") |n/a +Advanced |Supports Custom Alias |Required +Advanced |Private Key Handling |Required +Advanced |PFX Password Style |Default +Custom Fields|Google Cloud Platform Project Location/Region|Name:Location, Display Name:Location, Type:String, Default Value:global, Required:False +Custom Fields|The file name of the Google Cloud Service Account Key File installed in the same folder as the orchestrator extension. Empty if the orchestrator server resides in GCP and you are not using a service account key |Name:Service Account Key File Name, Type:String, Default Value: (leave blank), Required:True Entry Parameters|N/A| There are no Entry Parameters **Basic Settings:** @@ -51,6 +58,8 @@ Entry Parameters|N/A| There are no Entry Parameters **Custom Fields:** ![](images/CertStoreType-CustomFields.gif) +![](images/CertStoreType-CustomField-Location.gif) +![](images/CertStoreType-CustomField-ServiceAccountKey.gif) **Entry Params:** @@ -62,87 +71,21 @@ See Keyfactor InstallingKeyfactorOrchestrators.pdf Documentation. Get from your **3. Create a GCP Certificate Manager Certificate Store within Keyfactor Command** In Keyfactor Command create a new Certificate Store similar to the one below -![](images/CertStoreSettings-1.gif) -![](images/CertStoreSettings-2.gif) -![](images/GoogleCloudProjectInfo.gif) +![](images/CertStoreSettings.gif) #### STORE CONFIGURATION CONFIG ELEMENT |DESCRIPTION ----------------|--------------- Category |The type of certificate store to be configured. Select category based on the display name configured above "GCP Certificate Manager". Container |This is a logical grouping of like stores. This configuration is optional and does not impact the functionality of the store. -Client Machine |The name of the Google Certificate Manager Credentials File. This file should be stored in the same directory as the Orchestrator binary. Sample is "favorable-tree-346417-feb22d67de35.json". -Store Path |This will be the ProjectId of the Google Cloud Project. Sample here is "favorable-tree-346417". See above image. -Location|global is the default but could be another region based on the project. -Project Number| As shown in the above image, this can be obtained from the project information in Google Cloud. +Client Machine |Your GCP Project ID for your account. +Store Path |This is not used and should be defaulted to n/a per the certificate store type set up. Orchestrator |This is the orchestrator server registered with the appropriate capabilities to manage this certificate store type. -Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. +Location|**global** is the default but could be another region based on the project. +Service Account Key File Name | The name of the file containing the GCP Service Account JSON formatted key previously downloaded. Keep this optional field blank if the Keyfactor Orchestrator Service is running from an authenticated VM within Google Cloud. +Update Server User Name |Click and select No Value. +Update Server Password |Click and select No Value. Use SSL |This should be checked. -User |This is not necessary. -Password |This is not necessary. - -*** - -#### Usage - -**Adding New Certificate No Map Entry** - -![](images/AddCertificateNoMapEntry.gif) - -*** - -**Adding New Certificate With Map Entry** - -![](images/AddCertificateWithMapEntry.gif) - -*** - -**Replace Certficate With Map Entry** - -![](images/ReplaceCertificateMapEntry.gif) - -*** - -**Replace Certficate No Map Entry** - -![](images/ReplaceCertificateNoMapEntry.gif) - -*** - -**Replace Certficate With Map Entry** - -![](images/ReplaceCertificateMapEntry.gif) - -*** - -**Replace Certficate No Map Entry** - -![](images/ReplaceCertificateNoMapEntry.gif) - -*** - -**Remove Certificate Map Entry** - -![](images/RemoveCertifcateMapEntry.gif) - -*** - -**Remove Certficate No Map Entry** - -![](images/RemoveCertificateNoMapEntry.gif) - +Inventory Schedule |The interval that the system will use to report on what certificates are currently in the store. -#### TEST CASES -Case Number|Case Name|Case Description|Overwrite Flag|Alias Name|Expected Results|Passed -------------|---------|----------------|--------------|----------|----------------|-------------- -1|Fresh Add with New Map and Entry|Will create new map, map entry and cert|False|map12/mentry12/cert12|New Map will be created, New Map Entry Created, New Cert Created|True -1a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|map12/mentry12/cert12|Error Occurs Saying to Use Overwrite Flag|True -1b|Try Replace with Overwrite|Should delete and re-insert mapentry and certificate|True|map12/mentry12/cert12|Replaced Cert Map Entry and Certificate|True -2|Fresh Add with Cert Only (No Map)|Will create cert that is not tied to map|False|cert40|Created Certificate with alias cert40|True -2a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|Cert40|Error Occurs Saying to Use Overwrite Flag|True -2b|Try Replace with Overwrite|If user uses overwrite will replace cert|True|cert40|Certificate with be replaced with alias of cert40|True -3|Fresh Add with new entry to existing map|Will create cert where entry is tied to an existing map|False|map12/mentry50/cert50|Created Certificate with alias map12/mentry50/cert50|True -3a|Try Replace without Overwrite|If user does not use overwrite flag, should error out on same entry replace|False|map12/mentry50/cert50|Error Occurs Saying to Use Overwrite Flag|True -4|Remove Cert In Map|Try to remove cert in existing map. Should leave map and delete cert map entry and cert.|N/A|map12/mentry50/cert50|Cert cert50 and map entry mentry50 should be deleted.|True -4a|Remove Standalone cert (No Map)|Try to remove cert without a map entry or map.|N/A|cert40|Cert cert40 should be deleted.|True