diff --git a/.gitignore b/.gitignore index 8932a7e..6be482b 100644 --- a/.gitignore +++ b/.gitignore @@ -352,3 +352,4 @@ healthchecksdb logs *.pem *.crt +.claude/settings.local.json diff --git a/CHANGELOG.md b/CHANGELOG.md index e2b85e7..2525ce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +- 1.2.2 + - Fixed Sync Issues at CA Level, was ignoring and always syncing at pool level +- 1.2.1 + - Doc Updates - 1.2.0 - Added Enable Flag - Dual Build Support diff --git a/GCPCAS/Client/GCPCASClient.cs b/GCPCAS/Client/GCPCASClient.cs index e3ed3e3..392c2c7 100644 --- a/GCPCAS/Client/GCPCASClient.cs +++ b/GCPCAS/Client/GCPCASClient.cs @@ -59,7 +59,7 @@ public class GCPCASClient : IGCPCASClient /// The CA Pool ID in GCP CAS to use for certificate operations. If the CA Pool has resource name projects/my-project/locations/us-central1/caPools/my-pool, this field should be set to my-pool /// The CA ID of a CA in the same CA Pool as CAPool. For example, to issue certificates from a CA with resource name projects/my-project/locations/us-central1/caPools/my-pool/certificateAuthorities/my-ca, this field should be set to my-ca. public GCPCASClient(string locationId, string projectId, string caPool, string caId) - { + { _logger = LogHandler.GetClassLogger(); _logger.MethodEntry(); _logger.LogDebug($"Creating GCP CA Services Client with Location: {locationId}, Project ID: {projectId}, CA Pool: {caPool}, CA ID: {caId}"); @@ -84,13 +84,13 @@ public override string ToString() /// /// public Task Enable() - { + { _logger.MethodEntry(); if (!_clientIsEnabled) { _logger.LogDebug($"Enabling GCPCAS client {this.ToString()}"); _clientIsEnabled = true; - } + } _logger.MethodExit(); return Task.CompletedTask; } @@ -100,7 +100,7 @@ public Task Enable() /// /// public Task Disable() - { + { _logger.MethodEntry(); if (_clientIsEnabled) { @@ -118,12 +118,12 @@ public Task Disable() /// A indicating if the client is enabled. /// public bool IsEnabled() - { + { _logger.MethodEntry(); _logger.MethodExit(); return _clientIsEnabled; - } - + } + /// /// Attempts to connect to the GCP CAS service to verify connectivity. Verifies that the GCP Application Default Credentials are properly configured. /// @@ -131,53 +131,53 @@ public bool IsEnabled() /// Returns nothing if the connection is successful. /// /// Thrown if the GCP Application Default Credentials are not properly configured, if the GCP CAS CA Pool/CA is not found/is not compatible, or if the was not enabled via the method. - public async Task ValidateConnection() - { - _logger.MethodEntry(); - EnsureClientIsEnabled(); - - if (string.IsNullOrEmpty(_caId)) - { - _logger.LogTrace($"Validating CA Pool {_caPool} since no specific CA ID was provided"); - - CaPoolName poolName = new CaPoolName(_projectId, _locationId, _caPool); - CaPool pool = await _client.GetCaPoolAsync(poolName); - - if (pool.Tier != CaPool.Types.Tier.Enterprise) - { - string error = $"CA Pool {_caPool} is in Tier {pool.Tier}, expected {CaPool.Types.Tier.Enterprise}."; - _logger.LogError(error); - throw new Exception(error); - } - - _logger.LogDebug($"CA Pool {_caPool} is Enterprise tier and valid."); - _logger.MethodExit(); - return; - } - - _logger.LogTrace($"Searching for CA called {_caId} in CA Pool {_caPool}"); - CertificateAuthorityName caName = new CertificateAuthorityName(_projectId, _locationId, _caPool, _caId); - CertificateAuthority ca = await _client.GetCertificateAuthorityAsync(caName); - - _logger.LogDebug($"Found CA {ca.CertificateAuthorityName.CertificateAuthorityId} in CA Pool {ca.CertificateAuthorityName.CaPoolId}"); - - if (ca.State != CertificateAuthority.Types.State.Enabled) - { - string error = $"CA {_caId} is in state {ca.State}. Expected Enabled."; - _logger.LogError(error); - throw new Exception(error); - } - - if (ca.Tier != CaPool.Types.Tier.Enterprise) - { - string error = $"CA {_caId} is in tier {ca.Tier}. Only Enterprise tier is supported."; - _logger.LogError(error); - throw new Exception(error); - } - - _logger.LogDebug($"{nameof(GCPCASClient)} is compatible with CA {_caId} in Pool {_caPool}."); - _logger.MethodExit(); - } + public async Task ValidateConnection() + { + _logger.MethodEntry(); + EnsureClientIsEnabled(); + + if (string.IsNullOrEmpty(_caId)) + { + _logger.LogTrace($"Validating CA Pool {_caPool} since no specific CA ID was provided"); + + CaPoolName poolName = new CaPoolName(_projectId, _locationId, _caPool); + CaPool pool = await _client.GetCaPoolAsync(poolName); + + if (pool.Tier != CaPool.Types.Tier.Enterprise) + { + string error = $"CA Pool {_caPool} is in Tier {pool.Tier}, expected {CaPool.Types.Tier.Enterprise}."; + _logger.LogError(error); + throw new Exception(error); + } + + _logger.LogDebug($"CA Pool {_caPool} is Enterprise tier and valid."); + _logger.MethodExit(); + return; + } + + _logger.LogTrace($"Searching for CA called {_caId} in CA Pool {_caPool}"); + CertificateAuthorityName caName = new CertificateAuthorityName(_projectId, _locationId, _caPool, _caId); + CertificateAuthority ca = await _client.GetCertificateAuthorityAsync(caName); + + _logger.LogDebug($"Found CA {ca.CertificateAuthorityName.CertificateAuthorityId} in CA Pool {ca.CertificateAuthorityName.CaPoolId}"); + + if (ca.State != CertificateAuthority.Types.State.Enabled) + { + string error = $"CA {_caId} is in state {ca.State}. Expected Enabled."; + _logger.LogError(error); + throw new Exception(error); + } + + if (ca.Tier != CaPool.Types.Tier.Enterprise) + { + string error = $"CA {_caId} is in tier {ca.Tier}. Only Enterprise tier is supported."; + _logger.LogError(error); + throw new Exception(error); + } + + _logger.LogDebug($"{nameof(GCPCASClient)} is compatible with CA {_caId} in Pool {_caPool}."); + _logger.MethodExit(); + } /// @@ -195,84 +195,100 @@ public async Task ValidateConnection() /// /// Thrown if the is null or if the operation fails. /// - public async Task DownloadAllIssuedCertificates(BlockingCollection certificatesBuffer, CancellationToken cancelToken, DateTime? issuedAfter = null) - { - _logger.MethodEntry(); - EnsureClientIsEnabled(); - - if (certificatesBuffer == null) - { - string message = "Failed to download issued certificates - certificatesBuffer is null"; - _logger.LogError(message); - throw new ArgumentNullException(nameof(certificatesBuffer), message); - } - - _logger.LogTrace($"Setting up {typeof(ListCertificatesRequest).ToString()} with {this.ToString()}"); - - ListCertificatesRequest request = new ListCertificatesRequest - { - ParentAsCaPoolName = new CaPoolName(_projectId, _locationId, _caPool), - }; - - if (issuedAfter != null) - { - Timestamp ts = Timestamp.FromDateTime(issuedAfter.Value.ToUniversalTime()); - _logger.LogDebug($"Filtering issued certificates by update_time >= {ts}"); - request.Filter = $"update_time >= {ts}"; - } - - _logger.LogTrace($"Setting up {typeof(CallSettings).ToString()} with provided {typeof(CancellationToken).ToString()} {this.ToString()}"); - CallSettings settings = CallSettings.FromCancellationToken(cancelToken); - - _logger.LogDebug($"Downloading all issued certificates from GCP CAS {this.ToString()}"); - PagedAsyncEnumerable certificates = _client.ListCertificatesAsync(request, settings); - - int pageNumber = 0; - int numberOfCertificates = 0; - - try - { - await foreach (var response in certificates.AsRawResponses()) - { - if (response.Certificates == null) - { - _logger.LogWarning($"GCP returned null certificate list for page number {pageNumber} - continuing {this.ToString()}"); - continue; - } - - foreach (Certificate certificate in response.Certificates) - { - certificatesBuffer.Add(AnyCAPluginCertificateFromGCPCertificate(certificate)); - numberOfCertificates++; - _logger.LogDebug($"Found Certificate with name {certificate.CertificateName.CertificateId} {this.ToString()}"); - } - - _logger.LogTrace($"Fetched page {pageNumber} - Next Page Token: {response.NextPageToken}"); - pageNumber++; - } - } - catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted) - { - _logger.LogError($"Rate limit exceeded while fetching certificates: {ex.Message}"); - throw; - } - catch (OperationCanceledException) - { - _logger.LogWarning("Certificate download operation was canceled."); - throw; - } - catch (Exception ex) - { - _logger.LogError($"Unexpected error while fetching certificates: {ex.Message}"); - throw; - } - finally - { - certificatesBuffer.CompleteAdding(); - _logger.LogDebug($"Fetched {certificatesBuffer.Count} certificates from GCP over {pageNumber} pages."); - } - _logger.MethodExit(); - return numberOfCertificates; + public async Task DownloadAllIssuedCertificates(BlockingCollection certificatesBuffer, CancellationToken cancelToken, DateTime? issuedAfter = null) + { + _logger.MethodEntry(); + EnsureClientIsEnabled(); + + if (certificatesBuffer == null) + { + string message = "Failed to download issued certificates - certificatesBuffer is null"; + _logger.LogError(message); + throw new ArgumentNullException(nameof(certificatesBuffer), message); + } + + _logger.LogTrace($"Setting up {typeof(ListCertificatesRequest).ToString()} with {this.ToString()}"); + + ListCertificatesRequest request = new ListCertificatesRequest + { + ParentAsCaPoolName = new CaPoolName(_projectId, _locationId, _caPool), + }; + + string caFilter = null; + if (!string.IsNullOrEmpty(_caId)) + { + caFilter = _caId; + _logger.LogDebug($"Will filter certificates client-side by issuing CA ID: {caFilter}"); + } + + if (issuedAfter != null) + { + Timestamp ts = Timestamp.FromDateTime(issuedAfter.Value.ToUniversalTime()); + _logger.LogDebug($"Filtering issued certificates by update_time >= {ts}"); + request.Filter = $"update_time >= {ts}"; + } + + _logger.LogTrace($"Setting up {typeof(CallSettings).ToString()} with provided {typeof(CancellationToken).ToString()} {this.ToString()}"); + CallSettings settings = CallSettings.FromCancellationToken(cancelToken); + + _logger.LogDebug($"Downloading all issued certificates from GCP CAS {this.ToString()}"); + PagedAsyncEnumerable certificates = _client.ListCertificatesAsync(request, settings); + + int pageNumber = 0; + int numberOfCertificates = 0; + + try + { + await foreach (var response in certificates.AsRawResponses()) + { + if (response.Certificates == null) + { + _logger.LogWarning($"GCP returned null certificate list for page number {pageNumber} - continuing {this.ToString()}"); + continue; + } + + foreach (Certificate certificate in response.Certificates) + { + if (caFilter != null) + { + CertificateAuthorityName issuer = CertificateAuthorityName.Parse(certificate.IssuerCertificateAuthority); + if (issuer.CertificateAuthorityId != caFilter) + { + _logger.LogTrace($"Skipping certificate {certificate.CertificateName.CertificateId} - issued by {issuer.CertificateAuthorityId}, not {caFilter}"); + continue; + } + } + certificatesBuffer.Add(AnyCAPluginCertificateFromGCPCertificate(certificate)); + numberOfCertificates++; + _logger.LogDebug($"Found Certificate with name {certificate.CertificateName.CertificateId} {this.ToString()}"); + } + + _logger.LogTrace($"Fetched page {pageNumber} - Next Page Token: {response.NextPageToken}"); + pageNumber++; + } + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.ResourceExhausted) + { + _logger.LogError($"Rate limit exceeded while fetching certificates: {ex.Message}"); + throw; + } + catch (OperationCanceledException) + { + _logger.LogWarning("Certificate download operation was canceled."); + throw; + } + catch (Exception ex) + { + _logger.LogError($"Unexpected error while fetching certificates: {ex.Message}"); + throw; + } + finally + { + certificatesBuffer.CompleteAdding(); + _logger.LogDebug($"Fetched {certificatesBuffer.Count} certificates from GCP over {pageNumber} pages."); + } + _logger.MethodExit(); + return numberOfCertificates; } @@ -287,7 +303,7 @@ public async Task DownloadAllIssuedCertificates(BlockingCollection and task result as a containing the downloaded certificate. /// public async Task DownloadCertificate(string certificateId) - { + { _logger.MethodEntry(); EnsureClientIsEnabled(); @@ -300,13 +316,13 @@ public async Task DownloadCertificate(string certificate }; Certificate certificate = await _client.GetCertificateAsync(request); - _logger.LogTrace("GetCertificateAsync succeeded"); + _logger.LogTrace("GetCertificateAsync succeeded"); _logger.MethodExit(); return AnyCAPluginCertificateFromGCPCertificate(certificate); } private AnyCAPluginCertificate AnyCAPluginCertificateFromGCPCertificate(Certificate certificate) - { + { _logger.MethodEntry(); string productId = ""; if (certificate.CertificateTemplateAsCertificateTemplateName == null) @@ -328,7 +344,7 @@ private AnyCAPluginCertificate AnyCAPluginCertificateFromGCPCertificate(Certific revocationDate = certificate.RevocationDetails.RevocationTime.ToDateTime(); status = EndEntityStatus.REVOKED; revocationReason = (int)certificate.RevocationDetails.RevocationState; - } + } _logger.MethodExit(); return new AnyCAPluginCertificate { @@ -355,23 +371,23 @@ private AnyCAPluginCertificate AnyCAPluginCertificateFromGCPCertificate(Certific public async Task Enroll(ICreateCertificateRequestBuilder createCertificateRequestBuilder, CancellationToken cancelToken) { try - { + { _logger.MethodEntry(); EnsureClientIsEnabled(); CreateCertificateRequest request = createCertificateRequestBuilder.Build(_locationId, _projectId, _caPool, _caId); - if (request != null) - { - _logger.LogTrace($"Request Json {JsonConvert.SerializeObject(request)}"); + if (request != null) + { + _logger.LogTrace($"Request Json {JsonConvert.SerializeObject(request)}"); } Certificate certificate = await _client.CreateCertificateAsync(request, cancelToken); - if (certificate != null) - { - _logger.LogTrace($"Response Json {JsonConvert.SerializeObject(certificate)}"); - } + if (certificate != null) + { + _logger.LogTrace($"Response Json {JsonConvert.SerializeObject(certificate)}"); + } _logger.MethodExit(); return new EnrollmentResult { @@ -421,7 +437,7 @@ public async Task Enroll(ICreateCertificateRequestBuilder crea /// /// public Task RevokeCertificate(string certificateId, RevocationReason reason) - { + { _logger.MethodEntry(); EnsureClientIsEnabled(); @@ -444,7 +460,7 @@ public Task RevokeCertificate(string certificateId, RevocationReason reason) /// A of containing the available s. /// public List GetTemplates() - { + { _logger.MethodEntry(); EnsureClientIsEnabled();