Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -328,3 +328,5 @@ ASALocalRun/

# MFractors (Xamarin productivity tool) working folder
.mfractor/
.claude/settings.local.json
sample change.txt
23 changes: 13 additions & 10 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
# v2.0.0
* Migrate `packages.config` to `PackageReference` format
* Upgrade packages to support Keyfactor AnyCA Gateway DCOM v24.2
* Upgrade `Keyfactor.AnyGateway.SDK` to `24.2.0-PRERELEASE-47446`
* Add support for [GCP CAS Certificate Templates](https://cloud.google.com/certificate-authority-service/docs/policy-controls)
* Enable configuration of CA Pool-based or CA-specific certificate enrollment. If the `CAId` is specified, certificates are enrolled with the CA specified by `CAId`. Otherwise, GCP CAS selects a CA in the CA Pool based on policy.
# v1.0.2
* Fixed revocation status handling - failed revocations no longer incorrectly set certificate status to FAILED; certificate retains its current active status
* Added FlowLogger utility for structured flow diagrams across all public plugin methods
* Added guard clauses and input validation (null checks, UUID length validation before Substring)
* Added null response guards after all API calls
* Added null-safe structured logging throughout plugin, RequestManager, and HydrantIdClient
* Added AggregateException flattening in catch blocks for better error reporting
* Added per-certificate error isolation in Synchronize to prevent one bad cert from aborting sync
* Added BlockingCollection.IsAddingCompleted guard before CompleteAdding()
* Improved error handling in HydrantIdClient - non-success HTTP responses now throw with status details

# v1.1.0
- Remove template references from README
- Small bug fixes
# v1.0.1
* SaaS Containerization Fixes, added enabled flag cleaned up some log messages

# v1.0.0
* Initial Release. Support for Google GA CA Service. Sync, Enroll, and Revocation.
* Initial Release. Sync, Enroll, and Revocation.
413 changes: 292 additions & 121 deletions HydrantCAProxy/Client/HydrantIdClient.cs

Large diffs are not rendered by default.

145 changes: 145 additions & 0 deletions HydrantCAProxy/FlowLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
// Copyright 2025 Keyfactor
// Licensed under the Apache License, Version 2.0 (the "License"); you may
// not use this file except in compliance with the License. You may obtain a
// copy of the License at http://www.apache.org/licenses/LICENSE-2.0. Unless
// required by applicable law or agreed to in writing, software distributed
// under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License for
// the specific language governing permissions and limitations under the
// License.
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;

namespace Keyfactor.HydrantId
{
public sealed class FlowLogger : IDisposable
{
private readonly ILogger _logger;
private readonly string _flowName;
private readonly List<FlowStep> _steps = new List<FlowStep>();
private readonly Stopwatch _stopwatch;

public FlowLogger(ILogger logger, string flowName)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_flowName = flowName ?? "Unknown";
_stopwatch = Stopwatch.StartNew();
_logger.LogTrace("===== FLOW START: {FlowName} =====", _flowName);
}

public void Step(string name, string detail = null)
{
_steps.Add(new FlowStep(name, FlowStepStatus.Ok, detail));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: {Detail}", _flowName, name, detail ?? "OK");
}

public void Step(string name, Action action)
{
var sw = Stopwatch.StartNew();
try
{
action();
sw.Stop();
_steps.Add(new FlowStep(name, FlowStepStatus.Ok, $"{sw.ElapsedMilliseconds}ms"));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: OK ({Elapsed}ms)", _flowName, name, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
sw.Stop();
_steps.Add(new FlowStep(name, FlowStepStatus.Failed, ex.Message));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: FAILED - {Error}", _flowName, name, ex.Message);
throw;
}
}

public async Task StepAsync(string name, Func<Task> action)
{
var sw = Stopwatch.StartNew();
try
{
await action();
sw.Stop();
_steps.Add(new FlowStep(name, FlowStepStatus.Ok, $"{sw.ElapsedMilliseconds}ms"));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: OK ({Elapsed}ms)", _flowName, name, sw.ElapsedMilliseconds);
}
catch (Exception ex)
{
sw.Stop();
_steps.Add(new FlowStep(name, FlowStepStatus.Failed, ex.Message));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: FAILED - {Error}", _flowName, name, ex.Message);
throw;
}
}

public void Fail(string name, string reason)
{
_steps.Add(new FlowStep(name, FlowStepStatus.Failed, reason));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: FAILED - {Reason}", _flowName, name, reason);
}

public void Skip(string name, string reason)
{
_steps.Add(new FlowStep(name, FlowStepStatus.Skipped, reason));
_logger.LogTrace("[FLOW] {FlowName} -> {StepName}: SKIPPED - {Reason}", _flowName, name, reason);
}

public void Dispose()
{
_stopwatch.Stop();
var hasFailure = false;

_logger.LogTrace("===== FLOW DIAGRAM: {FlowName} =====", _flowName);
foreach (var step in _steps)
{
string icon;
switch (step.Status)
{
case FlowStepStatus.Ok:
icon = "[OK]";
break;
case FlowStepStatus.Failed:
icon = "[FAIL]";
hasFailure = true;
break;
case FlowStepStatus.Skipped:
icon = "[SKIP]";
break;
default:
icon = "[...]";
break;
}

var detail = string.IsNullOrEmpty(step.Detail) ? "" : $" ({step.Detail})";
_logger.LogTrace(" | {Icon} {StepName}{Detail}", icon, step.Name, detail);
_logger.LogTrace(" v");
}

var result = hasFailure ? "PARTIAL FAILURE" : "SUCCESS";
_logger.LogTrace("===== FLOW RESULT: {Result} ({Elapsed}ms) =====", result, _stopwatch.ElapsedMilliseconds);
}

private enum FlowStepStatus
{
Ok,
Failed,
Skipped
}

private class FlowStep
{
public string Name { get; }
public FlowStepStatus Status { get; }
public string Detail { get; }

public FlowStep(string name, FlowStepStatus status, string detail)
{
Name = name;
Status = status;
Detail = detail;
}
}
}
}
Loading
Loading