From a67d68809169f0f2411a0b8116990ae6033c8050 Mon Sep 17 00:00:00 2001 From: Hayden Date: Tue, 23 Jul 2024 20:03:56 +0000 Subject: [PATCH] chore(proxy): Support GCP CAS templates and add granularity to CA selection --- .../keyfactor-bootstrap-workflow.yml | 20 + .../workflows/keyfactor-starter-workflow.yml | 42 -- src/GoogleCAProxy/App.config | 23 +- src/GoogleCAProxy/GcpLogger.cs | 73 --- src/GoogleCAProxy/GoogleCAProxy.cs | 531 +++++++++++------- src/GoogleCAProxy/GoogleCAProxy.csproj | 51 +- src/GoogleCAProxy/GoogleCAProxy.sln | 10 +- src/GoogleCAProxy/app.config | 10 - src/GoogleCASandbox/GoogleCASandbox.csproj | 2 +- 9 files changed, 402 insertions(+), 360 deletions(-) create mode 100644 .github/workflows/keyfactor-bootstrap-workflow.yml delete mode 100644 .github/workflows/keyfactor-starter-workflow.yml delete mode 100644 src/GoogleCAProxy/GcpLogger.cs delete mode 100644 src/GoogleCAProxy/app.config diff --git a/.github/workflows/keyfactor-bootstrap-workflow.yml b/.github/workflows/keyfactor-bootstrap-workflow.yml new file mode 100644 index 0000000..2c1c440 --- /dev/null +++ b/.github/workflows/keyfactor-bootstrap-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@v3 + 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 }} \ No newline at end of file diff --git a/.github/workflows/keyfactor-starter-workflow.yml b/.github/workflows/keyfactor-starter-workflow.yml deleted file mode 100644 index 8d265c6..0000000 --- a/.github/workflows/keyfactor-starter-workflow.yml +++ /dev/null @@ -1,42 +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 - - get-manifest-properties: - runs-on: windows-latest - outputs: - update_catalog: ${{ steps.read-json.outputs.prop }} - steps: - - uses: actions/checkout@v3 - - name: Read json - id: read-json - shell: pwsh - run: | - $json = Get-Content integration-manifest.json | ConvertFrom-Json - echo "::set-output name=prop::$(echo $json.update_catalog)" - - 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: src/GoogleCAProxy/bin/Release - 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 - secrets: - token: ${{ secrets.APPROVE_README_PUSH }} - - call-update-catalog-workflow: - needs: get-manifest-properties - if: needs.get-manifest-properties.outputs.update_catalog == 'True' && (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/src/GoogleCAProxy/App.config b/src/GoogleCAProxy/App.config index f4419e2..5da3322 100644 --- a/src/GoogleCAProxy/App.config +++ b/src/GoogleCAProxy/App.config @@ -1,10 +1,19 @@  - - - - - - - + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/GoogleCAProxy/GcpLogger.cs b/src/GoogleCAProxy/GcpLogger.cs deleted file mode 100644 index b509f17..0000000 --- a/src/GoogleCAProxy/GcpLogger.cs +++ /dev/null @@ -1,73 +0,0 @@ -using CSS.Common.Logging; -using Grpc.Core.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace Keyfactor.AnyGateway.Google -{ - class GcpLogger : LoggingClientBase, ILogger - - { - public GcpLogger() - { - Logger.Info("Create Gcp Logger Instance"); - } - public void Debug(string message) - { - Logger.Debug(message); - } - - public void Debug(string format, params object[] formatArgs) - { - Logger.DebugFormat(format,formatArgs); - } - - public void Error(string message) - { - Logger.Error(message); - } - - public void Error(string format, params object[] formatArgs) - { - Logger.ErrorFormat(format, formatArgs); - } - - public void Error(Exception exception, string message) - { - Logger.Error($"{message} | {exception}"); - } - - public ILogger ForType() - { - return new GcpLogger(); - } - - public void Info(string message) - { - Logger.Info(message); - } - - public void Info(string format, params object[] formatArgs) - { - Logger.InfoFormat(format, formatArgs); - } - - public void Warning(string message) - { - Logger.Warn(message); - } - - public void Warning(string format, params object[] formatArgs) - { - Logger.WarnFormat(format, formatArgs); - } - - public void Warning(Exception exception, string message) - { - Logger.Warn($"{message} | {exception}"); - } - } -} diff --git a/src/GoogleCAProxy/GoogleCAProxy.cs b/src/GoogleCAProxy/GoogleCAProxy.cs index 7c74c05..ce6b0c5 100644 --- a/src/GoogleCAProxy/GoogleCAProxy.cs +++ b/src/GoogleCAProxy/GoogleCAProxy.cs @@ -1,52 +1,61 @@ -using CAProxy.AnyGateway; -using CAProxy.AnyGateway.Interfaces; -using CAProxy.AnyGateway.Models; -using CAProxy.Common; -using CSS.PKI; -using CSS.Common.Logging; -using System; +using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Security; using System.Threading; +using CAProxy.AnyGateway; +using CAProxy.AnyGateway.Interfaces; +using CAProxy.AnyGateway.Models; +using CAProxy.Common; +using CSS.PKI; +using Google.Api.Gax; using Google.Cloud.Security.PrivateCA.V1; using Google.Protobuf.WellKnownTypes; using Grpc.Core; -using Google.Api.Gax; +using Keyfactor.Logging; +using Microsoft.Extensions.Logging; namespace Keyfactor.AnyGateway.Google { public class GoogleCAProxy : BaseCAConnector { - const string AUTH_ENV_VARIABLE_NAME = "GOOGLE_APPLICATION_CREDENTIALS"; - const string PROJECT_ID_KEY = "ProjectId"; - const string LOCATION_ID_KEY = "LocationId"; - const string CA_ID_KEY = "CAId"; - const string CA_POOL_ID_KEY = "CAPoolId"; - const string LIFETIME_KEY = "Lifetime"; + private const string AuthEnvVariableName = "GOOGLE_APPLICATION_CREDENTIALS"; + private const string ProjectIdKey = "ProjectId"; + private const string LocationIdKey = "LocationId"; + private const string CaIdKey = "CAId"; + private const string CaPoolIdKey = "CAPoolId"; + private const string LifetimeKey = "Lifetime"; + private const string NoTemplateProductId = "Default"; + + private static readonly ILogger Log = LogHandler.GetClassLogger(); private CertificateAuthorityServiceClient GcpClient { get; set; } private ICAConnectorConfigProvider Config { get; set; } /// - /// Project Location ID from the Google Cloud Console for the Private CA Project + /// Project Location ID from the Google Cloud Console for the Private CA Project /// private string ProjectId { get; set; } + /// - /// Location ID (i.e. us-east1) from the Google Cloud Console for the Private CA deployment + /// Location ID (i.e. us-east1) from the Google Cloud Console for the Private CA deployment /// private string LocationId { get; set; } + /// - /// CA Resource ID from the Google Cloud Console for the Private CA to be monitored. To be marked obsolete at GA + /// CA Resource ID from the Google Cloud Console for the Private CA to be monitored. To be marked obsolete at GA /// - private string CAId { get; set; } + private string CaId { get; set; } + /// - /// CA Pool Resource ID from the Google Cloud Console. This will only be used in the V1 release + /// CA Pool Resource ID from the Google Cloud Console. This will only be used in the V1 release /// - private string CAPoolId { get; set; } + private string CaPoolId { get; set; } /// - /// AnyGateway method to enroll for a certificate from Google CA + /// AnyGateway method to enroll for a certificate from Google CA /// /// Database access to existing CA Certificates /// base64 encoded string of the Certificate Request @@ -56,91 +65,140 @@ public class GoogleCAProxy : BaseCAConnector /// /// /// - public override EnrollmentResult Enroll(ICertificateDataReader certificateDataReader, - string csr, - string subject, - Dictionary san, - EnrollmentProductInfo productInfo, - PKIConstants.X509.RequestFormat requestFormat, - RequestUtilities.EnrollmentType enrollmentType) + public override EnrollmentResult Enroll(ICertificateDataReader certificateDataReader, + string csr, + string subject, + Dictionary san, + EnrollmentProductInfo productInfo, + PKIConstants.X509.RequestFormat requestFormat, + RequestUtilities.EnrollmentType enrollmentType) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); + try { - GcpClient = BuildClient(); - - //var template = GcpClient.GetCertificateTemplate(productInfo.ProductID); - - //Logger.Trace($"Template {template.Name} found for enrollment"); - - var caPoolAsTypedName = CaPoolName.FromProjectLocationCaPool(ProjectId, LocationId, CAPoolId); - - Logger.Trace($"Enroll at CA Pool: {caPoolAsTypedName}"); + } + catch + { + Log.LogError("Failed to create GCP CAS client"); + throw; + } - if (!int.TryParse(productInfo.ProductParameters[LIFETIME_KEY], out int lifetimeInDays)) + int lifetimeInDays = 365; // Default value + if (productInfo.ProductParameters.TryGetValue(LifetimeKey, out string lifetimeInDaysString)) + { + if (!int.TryParse(lifetimeInDaysString, out lifetimeInDays)) { - Logger.Warn($"Unable to parse certificate {LIFETIME_KEY} from Product Parameters for Product Id {productInfo.ProductID}. Set Lifetime to 365 days."); - lifetimeInDays = 365; + Log.LogWarning( + $"Unable to parse certificate {LifetimeKey} from Product Parameters for Product Id {productInfo.ProductID}. Using default value of 365 days."); } - Logger.Trace($"Submit Certificate Request for {subject} with {lifetimeInDays} days validity"); - var certificate = new Certificate() - { - PemCsr = $"-----BEGIN NEW CERTIFICATE REQUEST-----\n{pemify(csr)}\n-----END NEW CERTIFICATE REQUEST-----", - Lifetime = Duration.FromTimeSpan(new TimeSpan(lifetimeInDays, 0, 0, 0, 0))//365 day default or defined by config - }; + } + else + { + Log.LogDebug( + $"LifetimeKey not found in Product Parameters for Product Id {productInfo.ProductID}. Using default value of 365 days."); + } - DateTime now = DateTime.Now; - var createCertificateRequest = new CreateCertificateRequest() { - ParentAsCaPoolName = caPoolAsTypedName, - Certificate = certificate, - //RequestId="",//if used, this needs to be durable between reties - CertificateId = $"{now:yyyy}{now:MM}{now:dd}-{now:HH}{now:mm}{now:ss}"//ID is required for Enterprise tier CAs and ignored for other. - }; + Log.LogDebug($"Configuring {typeof(Certificate)} for {subject} with {lifetimeInDays} days validity"); + Certificate certificate = new Certificate + { + PemCsr = + $"-----BEGIN NEW CERTIFICATE REQUEST-----\n{pemify(csr)}\n-----END NEW CERTIFICATE REQUEST-----", + Lifetime = Duration.FromTimeSpan(new TimeSpan(lifetimeInDays, 0, 0, 0, + 0)) //365 day default or defined by config + }; - var response = GcpClient.CreateCertificate(createCertificateRequest); + if (productInfo.ProductID == NoTemplateProductId) + { + Log.LogDebug( + $"{NoTemplateProductId} template selected - Certificate enrollment will defer to the baseline values and policy configured by the CA Pool."); + } + else + { + Log.LogDebug( + $"Configuring {typeof(Certificate)} with the {productInfo.ProductID} Certificate Template."); + CertificateTemplateName template = new CertificateTemplateName(ProjectId, LocationId, productInfo.ProductID); + certificate.CertificateTemplate = template.ToString(); + } - return new EnrollmentResult - { - Status = 20, - CARequestID = response.CertificateName?.CertificateId, - Certificate = response.PemCertificate - }; + DateTime now = DateTime.Now; + CaPoolName caPoolAsTypedName = CaPoolName.FromProjectLocationCaPool(ProjectId, LocationId, CaPoolId); + Log.LogDebug( + $"Configuring {typeof(CreateCertificateRequest)} with the configured {typeof(Certificate)} to enroll {subject} with the {caPoolAsTypedName} CA Pool"); + CreateCertificateRequest createCertificateRequest = new CreateCertificateRequest + { + ParentAsCaPoolName = caPoolAsTypedName, + Certificate = certificate, + //RequestId="",//if used, this needs to be durable between reties + CertificateId = + $"{now:yyyy}{now:MM}{now:dd}-{now:HH}{now:mm}{now:ss}" //ID is required for Enterprise tier CAs and ignored for other. + }; + + if (!string.IsNullOrEmpty(CaId)) + { + Log.LogDebug( + $"CAConnection section contained a non-empty CAId - Certificate will be enrolled using the CA with ID {CaId}"); + createCertificateRequest.IssuingCertificateAuthorityId = CaId; + } + + Certificate response; + try + { + Log.LogDebug($"Submitting CreateCertificate RPC for {subject}"); + response = GcpClient.CreateCertificate(createCertificateRequest); + Log.LogDebug($"RPC was successful - minted certificate with resource name {response.Name}"); } catch (RpcException gEx) { + string message = + $"Could not complete certificate enrollment. RPC was unsuccessful. Status Code: {gEx.StatusCode} | Detail: {gEx.Status.Detail}"; + Log.LogError(message); return new EnrollmentResult { Status = 30, - StatusMessage = $"Could not complete certificate enrollment. Status Code: {gEx.StatusCode} | Detail: {gEx.Status.Detail}" + StatusMessage = message }; } catch (Exception ex) { - return new EnrollmentResult { - Status=30, - StatusMessage = $"Could not complete certificate enrollment. {ex.Message}" + string message = $"Exception caught - Could not complete certificate enrollment: {ex}"; + Log.LogError(message); + return new EnrollmentResult + { + Status = 30, + StatusMessage = message }; } + + return new EnrollmentResult + { + Status = 20, + CARequestID = response.CertificateName?.CertificateId, + Certificate = response.PemCertificate + }; } + /// - /// AnyGateway method to get a single certificate's detail from the CA + /// AnyGateway method to get a single certificate's detail from the CA /// /// CA Id returned during inital synchronization /// public override CAConnectorCertificate GetSingleRecord(string caRequestID) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); try { GcpClient = BuildClient(); - var cloudCert = GcpClient.GetCertificate(new CertificateName(ProjectId, LocationId, CAPoolId, caRequestID)); + Certificate cloudCert = + GcpClient.GetCertificate(new CertificateName(ProjectId, LocationId, CaPoolId, caRequestID)); return ProcessCAResponse(cloudCert); } catch (RpcException gEx) { - throw new Exception($"Could not retrieve certificate. Status Code: {gEx.StatusCode} | Detail: {gEx.Status.Detail}"); + throw new Exception( + $"Could not retrieve certificate. Status Code: {gEx.StatusCode} | Detail: {gEx.Status.Detail}"); } catch (Exception ex) { @@ -149,38 +207,37 @@ public override CAConnectorCertificate GetSingleRecord(string caRequestID) } /// - /// AnyGateway method called before most AnyGateway functions + /// AnyGateway method called before most AnyGateway functions /// /// Existing configuration extracted from the AnyGateway database public override void Initialize(ICAConnectorConfigProvider configProvider) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); try { Config = configProvider; - ProjectId = Config.CAConnectionData[PROJECT_ID_KEY] as string; - LocationId = Config.CAConnectionData[LOCATION_ID_KEY] as string; - CAPoolId = Config.CAConnectionData[CA_POOL_ID_KEY] as string; - CAId = Config.CAConnectionData[CA_ID_KEY] as string; + ProjectId = Config.CAConnectionData[ProjectIdKey] as string; + LocationId = Config.CAConnectionData[LocationIdKey] as string; + CaPoolId = Config.CAConnectionData[CaPoolIdKey] as string; + CaId = Config.CAConnectionData[CaIdKey] as string; } catch (Exception ex) { - Logger.Error(ex); + Log.LogError($"Failed to initialize GCP CAS CAPlugin: {ex}"); } } /// - /// Certutil response to the certutil -ping [-config host\logical] command + /// Certutil response to the certutil -ping [-config host\logical] command /// public override void Ping() { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); - + Log.MethodEntry(); + Log.MethodExit(); } /// - /// AnyGateway method to revoke a certificate + /// AnyGateway method to revoke a certificate /// /// /// @@ -188,142 +245,148 @@ public override void Ping() /// public override int Revoke(string caRequestID, string hexSerialNumber, uint revocationReason) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); try { GcpClient = BuildClient(); - CertificateName certId = new CertificateName(ProjectId, LocationId, CAPoolId, caRequestID); + CertificateName certId = new CertificateName(ProjectId, LocationId, CaPoolId, caRequestID); - RevokeCertificateRequest request = new RevokeCertificateRequest() - { + RevokeCertificateRequest request = new RevokeCertificateRequest + { CertificateName = certId, Reason = (RevocationReason)revocationReason }; - Logger.Trace($"Revoking certificate id {certId}"); - var response = GcpClient.RevokeCertificate(request); + Log.LogTrace($"Revoking certificate id {certId}"); + Certificate response = GcpClient.RevokeCertificate(request); return Convert.ToInt32(PKIConstants.Microsoft.RequestDisposition.REVOKED); ; } catch (RpcException gEx) { - Logger.Error($"Unable to revoke certificate. Status Code: {gEx.StatusCode} | Status:{gEx.Status}"); + Log.LogError($"Unable to revoke certificate. Status Code: {gEx.StatusCode} | Status:{gEx.Status}"); throw gEx; } catch (Exception ex) { - Logger.Error($"Unable to revoke certificate. {ex.Message}"); + Log.LogError($"Unable to revoke certificate. {ex.Message}"); throw ex; } } /// - /// AnyGateway method to syncronize Google CA Certificates + /// AnyGateway method to syncronize Google CA Certificates /// /// Database access to the current certificates for the CA /// /// Detail about the CA being synchronized /// public override void Synchronize(ICertificateDataReader certificateDataReader, - BlockingCollection blockingBuffer, - CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, - CancellationToken cancelToken) + BlockingCollection blockingBuffer, + CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, + CancellationToken cancelToken) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); try { GcpClient = BuildClient(); //For sync we still need to specify the CA ID since the pool will not provide a list of certs. //Do we have a CA in Keyfactor for each even thought issuance and revocation will be pool level? Probably - CertificateAuthorityName caName = CertificateAuthorityName.FromProjectLocationCaPoolCertificateAuthority(ProjectId, LocationId, CAPoolId, CAId); + CertificateAuthorityName caName = + CertificateAuthorityName.FromProjectLocationCaPoolCertificateAuthority(ProjectId, LocationId, + CaPoolId, CaId); if (certificateAuthoritySyncInfo.DoFullSync) { - var ca = GcpClient.GetCertificateAuthority(caName); - ProcessCACertificateList(ca, blockingBuffer, cancelToken); + CertificateAuthority ca = GcpClient.GetCertificateAuthority(caName); + ProcessCACertificateList(ca, blockingBuffer, cancelToken); } - ListCertificatesRequest syncRequest = new ListCertificatesRequest() + ListCertificatesRequest syncRequest = new ListCertificatesRequest { - ParentAsCaPoolName = CaPoolName.FromProjectLocationCaPool(ProjectId,LocationId,CAPoolId), + ParentAsCaPoolName = CaPoolName.FromProjectLocationCaPool(ProjectId, LocationId, CaPoolId) }; if (!certificateAuthoritySyncInfo.DoFullSync) { - Timestamp lastSyncTime = certificateAuthoritySyncInfo.LastFullSync.Value.ToUniversalTime().ToTimestamp(); - Logger.Trace($"Executing an incremental sync. Filter list by update_time >= {lastSyncTime.ToDateTime().ToLocalTime()}"); + Timestamp lastSyncTime = + certificateAuthoritySyncInfo.LastFullSync.Value.ToUniversalTime().ToTimestamp(); + Log.LogTrace( + $"Executing an incremental sync. Filter list by update_time >= {lastSyncTime.ToDateTime().ToLocalTime()}"); syncRequest.Filter = $"update_time >= {lastSyncTime}"; } - var responseList = GcpClient.ListCertificates(syncRequest); + PagedEnumerable responseList = + GcpClient.ListCertificates(syncRequest); ProcessCertificateList(responseList, blockingBuffer, cancelToken); } catch (RpcException gEx) { - Logger.Error($"Unable to get CA Certificate List. {gEx.StatusCode} | {gEx.Status}"); + Log.LogError($"Unable to get CA Certificate List. {gEx.StatusCode} | {gEx.Status}"); } catch (Exception ex) { - Logger.Error($"Unhandled Exception: {ex}"); + Log.LogError($"Unhandled Exception: {ex}"); } } /// - /// AnyGateway method to validate connection detail (CAConnection section) during the Set-KeyfactorGatewayConfig cmdlet + /// AnyGateway method to validate connection detail (CAConnection section) during the Set-KeyfactorGatewayConfig cmdlet /// /// CAConnection section of the AnyGateway JSON file public override void ValidateCAConnectionInfo(Dictionary connectionInfo) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - //connectionInfo is the currently imported values - //CONFIG is the existing configuration from the initalize + Log.MethodEntry(); List errors = new List(); - Logger.Trace("Checking required CAConnection config"); - errors.AddRange(CheckRequiredValues(connectionInfo, PROJECT_ID_KEY, LOCATION_ID_KEY, CA_POOL_ID_KEY, CA_ID_KEY)); + Log.LogTrace("Checking required CAConnection config"); + errors.AddRange(CheckRequiredValues(connectionInfo, ProjectIdKey, LocationIdKey, CaPoolIdKey)); - Logger.Trace("Checking permissions for JSON license file"); + Log.LogTrace("Checking permissions for JSON license file"); errors.AddRange(CheckEnvrionmentVariables()); - Logger.Trace("Checking connectivity and CA type"); + Log.LogTrace("Checking connectivity and CA type"); errors.AddRange(CheckCAConfig(connectionInfo)); - if (errors.Any()) - { - throw new Exception(String.Join("|", errors.ToArray())); - } + if (errors.Any()) throw new Exception(string.Join("|", errors.ToArray())); } /// - /// AnyGateway method to validate product info (Template section) during the Set-KeyfactorGatewayConfig cmdlet + /// AnyGateway method to validate product info (Template section) during the Set-KeyfactorGatewayConfig cmdlet /// /// Parameters section of the AnyGateway JSON file /// CAConnection section of the AnyGateway JSON file - public override void ValidateProductInfo(EnrollmentProductInfo productInfo, Dictionary connectionInfo) + public override void ValidateProductInfo(EnrollmentProductInfo productInfo, + Dictionary connectionInfo) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); //TODO: Evaluate Template (if avaiable) based on ProductInfo //https://cloud.google.com/certificate-authority-service/docs/reference/rest/v1/projects.locations.certificateTemplates#CertificateTemplate - Logger.MethodExit(ILogExtensions.MethodLogLevel.Debug); + Log.MethodExit(); } #region Private Helper Methods + /// - /// Method to process Issued certificates from the Goocle CA + /// Method to process Issued certificates from the Goocle CA /// - /// from a full or incremental sync request to the Google CA + /// + /// from a full or incremental sync request to the + /// Google CA + /// /// /// - private void ProcessCertificateList(PagedEnumerable responseList, BlockingCollection blockingBuffer, CancellationToken cancelToken) + private void ProcessCertificateList(PagedEnumerable responseList, + BlockingCollection blockingBuffer, CancellationToken cancelToken) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); int totalCertsProcessed = 0; int pagesProcessed = 0; - foreach (var page in responseList.AsRawResponses()) + foreach (ListCertificatesResponse page in responseList.AsRawResponses()) { if (page.Count() == 0) { - Logger.Warn($"Incremental Sync Returned No Results"); + Log.LogWarning("Incremental Sync Returned No Results"); continue; } @@ -332,7 +395,7 @@ private void ProcessCertificateList(PagedEnumerable 0) - { - Logger.Warn($"Adding of {caCert.CARequestID} to queue was blocked. Took a total of {blockedCount} tries to process."); - } + Log.LogWarning( + $"Adding of {caCert.CARequestID} to queue was blocked. Took a total of {blockedCount} tries to process."); pageCertsProcessed++; totalCertsProcessed++; } @@ -350,26 +412,30 @@ private void ProcessCertificateList(PagedEnumerable - /// Method to process the Issuing Certificate of a Google CA + /// Method to process the Issuing Certificate of a Google CA /// - /// to process certificate from + /// to process certificate from /// BlockingCollection provided by the Command platform for syncing CA certificates /// - private void ProcessCACertificateList(CertificateAuthority ca, BlockingCollection blockingBuffer, CancellationToken cancelToken) + private void ProcessCACertificateList(CertificateAuthority ca, + BlockingCollection blockingBuffer, CancellationToken cancelToken) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); + Log.MethodEntry(); int caCertsProcessed = 0; do { - var caPemCert = ca.PemCaCertificates.ElementAt(caCertsProcessed); + string caPemCert = ca.PemCaCertificates.ElementAt(caCertsProcessed); CAConnectorCertificate caCert = new CAConnectorCertificate { @@ -384,57 +450,112 @@ private void ProcessCACertificateList(CertificateAuthority ca, BlockingCollectio if (blockingBuffer.TryAdd(caCert, 50, cancelToken)) { if (blockedCount > 0) - { - Logger.Warn($"Adding of {caCert.CARequestID} to queue was blocked. Took a total of {blockedCount} tries to process."); - } + Log.LogWarning( + $"Adding of {caCert.CARequestID} to queue was blocked. Took a total of {blockedCount} tries to process."); caCertsProcessed++; } else { blockedCount++; } - } while (caCertsProcessed < (ca.PemCaCertificates.Count - 1) ); + } while (caCertsProcessed < ca.PemCaCertificates.Count - 1); } - /// - /// Validate CA Configuration by attempting to connect and validate - /// - /// CAConnection Details object from the AnyGateway Config JSON file - /// - private static IEnumerable CheckCAConfig(Dictionary connectionInfo) + private static IEnumerable ValidateCaPool(Dictionary connectionInfo) { List returnValue = new List(); try { - var ca = BuildClient().GetCertificateAuthority(new GetCertificateAuthorityRequest + Log.LogDebug($"Validating that service account can access CA Pool with ID {connectionInfo[CaPoolIdKey] as string}"); + CaPool caPool = BuildClient().GetCaPool(new GetCaPoolRequest() { - CertificateAuthorityName = CertificateAuthorityName.FromProjectLocationCaPoolCertificateAuthority( - connectionInfo[PROJECT_ID_KEY] as string, - connectionInfo[LOCATION_ID_KEY] as string, - connectionInfo[CA_POOL_ID_KEY] as string, - connectionInfo[CA_ID_KEY] as string + CaPoolName = CaPoolName.FromProjectLocationCaPool( + connectionInfo[ProjectIdKey] as string, + connectionInfo[LocationIdKey] as string, + connectionInfo[CaPoolIdKey] as string ) }); - if (ca.Tier == CaPool.Types.Tier.Devops) - { - returnValue.Add($"{ca.Tier} is an unsupported CA configuration"); - } + if (caPool.Tier == CaPool.Types.Tier.Devops) + { + string message = $"{caPool.Tier} is an unsupported CA configuration"; + Log.LogError(message); + returnValue.Add(message); + } + } + catch (RpcException gEx) + { + string message = $"Unable to connect to CA Pool. Status Code: {gEx.StatusCode} | Status: {gEx.Status}"; + Log.LogError(message); + returnValue.Add(message); + } + catch (Exception ex) + { + string message = $"Unable to connect to CA. Detail: {ex.Message}"; + Log.LogError(message); + returnValue.Add(message); + } + + return returnValue; + } + + private static IEnumerable ValidateCa(Dictionary connectionInfo) + { + List returnValue = new List(); + try + { + Log.LogDebug($"Validating that service account can access CA with ID {connectionInfo[CaIdKey] as string}"); + CertificateAuthority ca = BuildClient().GetCertificateAuthority(new GetCertificateAuthorityRequest + { + CertificateAuthorityName = CertificateAuthorityName.FromProjectLocationCaPoolCertificateAuthority( + connectionInfo[ProjectIdKey] as string, + connectionInfo[LocationIdKey] as string, + connectionInfo[CaPoolIdKey] as string, + connectionInfo[CaIdKey] as string + ) + }); + + if (ca.Tier == CaPool.Types.Tier.Devops) + { + string message = $"{ca.Tier} is an unsupported CA configuration"; + Log.LogError(message); + returnValue.Add(message); + } } catch (RpcException gEx) { - returnValue.Add($"Unable to connect to CA. Status Code: {gEx.StatusCode} | Status: {gEx.Status}"); + string message = $"Unable to connect to CA. Status Code: {gEx.StatusCode} | Status: {gEx.Status}"; + Log.LogError(message); + returnValue.Add(message); } catch (Exception ex) { - returnValue.Add($"Unable to connect to CA. Detail: {ex.Message}"); + string message = $"Unable to connect to CA. Detail: {ex.Message}"; + Log.LogError(message); + returnValue.Add(message); } return returnValue; } + /// + /// Validate CA Configuration by attempting to connect and validate + /// + /// CAConnection Details object from the AnyGateway Config JSON file + /// + private static IEnumerable CheckCAConfig(Dictionary connectionInfo) + { + if (connectionInfo.TryGetValue(CaIdKey, out object id) && !string.IsNullOrEmpty(id as string)) + { + Log.LogDebug($"GCP CAS CAProxy configured with a non-empty {CaIdKey} - validating that {id as string} exists."); + return ValidateCa(connectionInfo); + } + + Log.LogDebug($"GCP CAS CAProxy configured with an empty or non-existant {CaIdKey} - validating that CA pool exists."); + return ValidateCaPool(connectionInfo); + } /// - /// Determines if the provided keys have been configured + /// Determines if the provided keys have been configured /// /// CAConnection Details object from the AnyGateway Config JSON file /// List of keys to validate @@ -443,96 +564,116 @@ private static List CheckRequiredValues(Dictionary conne { List errors = new List(); foreach (string s in args) - { - if (String.IsNullOrEmpty(connectionInfo[s] as string)) + if (string.IsNullOrEmpty(connectionInfo[s] as string)) errors.Add($"{s} is a required value"); - } return errors; } /// - /// Determines if the AnyGateway service can read from the GOOGLE_APPLICATION_CREDENTIALS machine envrionment variable and read the contents of the - /// file. + /// Determines if the AnyGateway service can read from the GOOGLE_APPLICATION_CREDENTIALS machine envrionment variable + /// and read the contents of the + /// file. /// - /// the contains any error messages for items failing validation + /// the contains any error messages for items failing validation private static List CheckEnvrionmentVariables() { List errors = new List(); try { - string envrionmentVariablePath = Environment.GetEnvironmentVariable(AUTH_ENV_VARIABLE_NAME, EnvironmentVariableTarget.Machine); - if (String.IsNullOrEmpty(envrionmentVariablePath)) - errors.Add($"{AUTH_ENV_VARIABLE_NAME} must be conifgured with a JSON credential file"); + string envrionmentVariablePath = + Environment.GetEnvironmentVariable(AuthEnvVariableName, EnvironmentVariableTarget.Machine); + if (string.IsNullOrEmpty(envrionmentVariablePath)) + { + string message = $"{AuthEnvVariableName} must be conifgured with a JSON credential file"; + Log.LogError(message); + errors.Add(message); + } if (!envrionmentVariablePath.IsFullPathReadable()) - errors.Add($"Cannot read license file at {envrionmentVariablePath}"); + { + string message = $"Cannot read license file at {envrionmentVariablePath}"; + Log.LogError(message); + errors.Add(message); + } } - catch (System.Security.SecurityException) + catch (SecurityException) { - errors.Add($"Access denied to {AUTH_ENV_VARIABLE_NAME} at \"HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment\" registry key"); + string message = + $"Access denied to {AuthEnvVariableName} at \"HKLM\\System\\CurrentControlSet\\Control\\Session Manager\\Environment\" registry key"; + Log.LogError(message); + errors.Add(message); } return errors; } + /// - /// Creates a Keyfactor AnyGateway Certificate Type from the GCP Certificate Type + /// Creates a Keyfactor AnyGateway Certificate Type from the GCP Certificate Type /// /// - /// parsed from a object + /// parsed from a object private CAConnectorCertificate ProcessCAResponse(Certificate caCertificate) { - Logger.MethodEntry(ILogExtensions.MethodLogLevel.Debug); - return new CAConnectorCertificate() + Log.MethodEntry(); + return new CAConnectorCertificate { - CARequestID = caCertificate.CertificateName.CertificateId,//limited to 100 characters. use cert id only Required + CARequestID = + caCertificate.CertificateName.CertificateId, //limited to 100 characters. use cert id only Required CSR = caCertificate.PemCsr, Certificate = caCertificate.PemCertificate, - Status = caCertificate.RevocationDetails is null ? 20 : 21,//required - SubmissionDate = caCertificate.CreateTime?.ToDateTime(),//Required + Status = caCertificate.RevocationDetails is null ? 20 : 21, //required + SubmissionDate = caCertificate.CreateTime?.ToDateTime(), //Required ResolutionDate = caCertificate.CreateTime?.ToDateTime(), RevocationDate = caCertificate.RevocationDetails?.RevocationTime.ToDateTime(), - RevocationReason = caCertificate.RevocationDetails is null ? -1 : (int)caCertificate.RevocationDetails.RevocationState //assumes revocation reasons match Keyfactor + RevocationReason = caCertificate.RevocationDetails is null + ? -1 + : (int)caCertificate.RevocationDetails.RevocationState //assumes revocation reasons match Keyfactor }; - } /// - /// Add new line every 64 characters to propertly format a base64 string as PEM + /// Add new line every 64 characters to propertly format a base64 string as PEM /// - private static Func pemify = (ss => ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + pemify(ss.Substring(64))); - + private static readonly Func pemify = ss => + ss.Length <= 64 ? ss : ss.Substring(0, 64) + "\n" + pemify(ss.Substring(64)); + /// - /// Build a new instance of the CertificateAuthorityServiceClient with explict credentials from the Server Envrionment Variable + /// Build a new instance of the CertificateAuthorityServiceClient with explict credentials from the Server Envrionment + /// Variable /// /// private static CertificateAuthorityServiceClient BuildClient() { - var caClient = new CertificateAuthorityServiceClientBuilder + CertificateAuthorityServiceClientBuilder caClient = new CertificateAuthorityServiceClientBuilder { - CredentialsPath = Environment.GetEnvironmentVariable(AUTH_ENV_VARIABLE_NAME, EnvironmentVariableTarget.Machine) + CredentialsPath = + Environment.GetEnvironmentVariable(AuthEnvVariableName, EnvironmentVariableTarget.Machine) }; return caClient.Build(); } - #endregion #region Obsolete Methods + [Obsolete] - public override EnrollmentResult Enroll(string csr, string subject, Dictionary san, EnrollmentProductInfo productInfo, CSS.PKI.PKIConstants.X509.RequestFormat requestFormat, RequestUtilities.EnrollmentType enrollmentType) + public override EnrollmentResult Enroll(string csr, string subject, Dictionary san, + EnrollmentProductInfo productInfo, PKIConstants.X509.RequestFormat requestFormat, + RequestUtilities.EnrollmentType enrollmentType) { throw new NotImplementedException(); } [Obsolete] public override void Synchronize(ICertificateDataReader certificateDataReader, - BlockingCollection blockingBuffer, - CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, - CancellationToken cancelToken, - string logicalName) + BlockingCollection blockingBuffer, + CertificateAuthoritySyncInfo certificateAuthoritySyncInfo, + CancellationToken cancelToken, + string logicalName) { throw new NotImplementedException(); } + #endregion } -} +} \ No newline at end of file diff --git a/src/GoogleCAProxy/GoogleCAProxy.csproj b/src/GoogleCAProxy/GoogleCAProxy.csproj index f74eef7..5e5929c 100644 --- a/src/GoogleCAProxy/GoogleCAProxy.csproj +++ b/src/GoogleCAProxy/GoogleCAProxy.csproj @@ -10,17 +10,16 @@ Keyfactor.AnyGateway.Google GoogleCAProxy v4.7.2 - net472 512 true - - + OnBuildSuccess + true true full false - bin\Debug\ + bin\Debug DEBUG;TRACE prompt 4 @@ -34,20 +33,20 @@ 4 - true + Powershell C:\Users\kfadmin\Documents\StopCaGateway.ps1 - - bin\Prerelease\ + + Powershell C:\Users\kfadmin\Documents\StartCaGateway.ps1 - - $(NugetPackagesPath)Keyfactor.AnyGateway.SDK.24.2.0-prerelease-47446\lib\net472\CAProxy.AnyGateway.Core.dll + + packages\Keyfactor.AnyGateway.SDK.24.2.0.24.2.0-PRERELEASE-47446\lib\net472\CAProxy.AnyGateway.Core.dll - - ..\packages\Keyfactor.AnyGateway.SDK.24.2.0-prerelease-47446\lib\net472\CAProxy.Interfaces.dll + + packages\Keyfactor.AnyGateway.SDK.24.2.0.24.2.0-PRERELEASE-47446\lib\net472\CAProxy.Interfaces.dll - - $(NugetPackagesPath)Keyfactor.AnyGateway.SDK.24.2.0-prerelease-47446\lib\net472\CommonCAProxy.dll + + packages\Keyfactor.AnyGateway.SDK.24.2.0.24.2.0-PRERELEASE-47446\lib\net472\CommonCAProxy.dll @@ -60,39 +59,37 @@ - - - - - + - - - - - - - + all runtime; build; native; contentfiles; analyzers; buildtransitive + + - + + + - + + + + + diff --git a/src/GoogleCAProxy/GoogleCAProxy.sln b/src/GoogleCAProxy/GoogleCAProxy.sln index fd2ea43..e778ab5 100644 --- a/src/GoogleCAProxy/GoogleCAProxy.sln +++ b/src/GoogleCAProxy/GoogleCAProxy.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.30611.23 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35027.167 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleCAProxy", "GoogleCAProxy.csproj", "{011DC646-BEF9-4D3B-9D20-CA444A26B355}" EndProject @@ -14,7 +14,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ..\..\README.md = ..\..\README.md EndProjectSection EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "GoogleCASandbox", "..\GoogleCASandbox\GoogleCASandbox.csproj", "{863DC1E0-8E93-4EE6-8404-75C34838FF2A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GoogleCASandbox", "..\GoogleCASandbox\GoogleCASandbox.csproj", "{863DC1E0-8E93-4EE6-8404-75C34838FF2A}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -25,8 +25,8 @@ Global GlobalSection(ProjectConfigurationPlatforms) = postSolution {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Debug|Any CPU.Build.0 = Debug|Any CPU - {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Prerelease|Any CPU.ActiveCfg = Prerelease|Any CPU - {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Prerelease|Any CPU.Build.0 = Prerelease|Any CPU + {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Prerelease|Any CPU.ActiveCfg = Release|Any CPU + {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Prerelease|Any CPU.Build.0 = Release|Any CPU {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Release|Any CPU.ActiveCfg = Release|Any CPU {011DC646-BEF9-4D3B-9D20-CA444A26B355}.Release|Any CPU.Build.0 = Release|Any CPU {863DC1E0-8E93-4EE6-8404-75C34838FF2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU diff --git a/src/GoogleCAProxy/app.config b/src/GoogleCAProxy/app.config deleted file mode 100644 index f4419e2..0000000 --- a/src/GoogleCAProxy/app.config +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - \ No newline at end of file diff --git a/src/GoogleCASandbox/GoogleCASandbox.csproj b/src/GoogleCASandbox/GoogleCASandbox.csproj index f362557..b2b35d4 100644 --- a/src/GoogleCASandbox/GoogleCASandbox.csproj +++ b/src/GoogleCASandbox/GoogleCASandbox.csproj @@ -90,7 +90,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive