Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
0df7f5b
Implemented enrollment, added helper methods and API response handlers
joevanwanzeeleKF Nov 11, 2025
a13df0d
implemented cert retrieval methods on client.
joevanwanzeeleKF Nov 12, 2025
3f4fe3c
added additional logging, implemented revoke on client
joevanwanzeeleKF Nov 13, 2025
d3edb38
implemented sync and revoke; completed initial functionality
joevanwanzeeleKF Nov 17, 2025
8513944
added changelog and license headers
joevanwanzeeleKF Nov 18, 2025
379b500
added manifest, disabled auth cert domain check for nexus auth cert
joevanwanzeeleKF Nov 20, 2025
a6a8859
Updated enrollment to include first available procname for enrollment…
joevanwanzeeleKF Dec 2, 2025
976d0f6
updated request format for revocation
joevanwanzeeleKF Dec 2, 2025
4fc46a3
cleanup
joevanwanzeeleKF Dec 2, 2025
e78dd82
documentation updates
joevanwanzeeleKF Dec 3, 2025
23297b3
updated project settings for github build
joevanwanzeeleKF Dec 3, 2025
14828d8
added keyfactor-bootstrap-workflow.yml
joevanwanzeeleKF Dec 4, 2025
f71e0aa
updated manifest
joevanwanzeeleKF Dec 4, 2025
c5b6d33
Merge branch 'initial_AB#64146' of https://github.com/Keyfactor/nexus…
joevanwanzeeleKF Dec 4, 2025
da3557d
added docsource folder
joevanwanzeeleKF Dec 17, 2025
072b739
corrected the returned value on a revoke request
joevanwanzeeleKF Dec 19, 2025
9457add
Update nexus-certificate-manager-caplugin/NexusCertManagerCAPlugin.cs
joevanwanzeeleKF Jan 16, 2026
34f633a
Update docsource/configuration.md
joevanwanzeeleKF Jan 16, 2026
e3c0433
Update nexus-certificate-manager-caplugin/NexusCertManagerClient.cs
joevanwanzeeleKF Jan 16, 2026
a950dd9
Update nexus-certificate-manager-caplugin/models/Helpers.cs
joevanwanzeeleKF Jan 16, 2026
f7d5ca5
Update nexus-certificate-manager-caplugin/NexusCertManagerCAPlugin.cs
joevanwanzeeleKF Jan 16, 2026
5c8db48
Update nexus-certificate-manager-caplugin/NexusCertManagerClient.cs
joevanwanzeeleKF Jan 16, 2026
0f5334e
Update nexus-certificate-manager-caplugin/NexusCertManagerCAPlugin.cs
joevanwanzeeleKF Jan 16, 2026
162d092
Update nexus-certificate-manager-caplugin/NexusCertManagerClient.cs
joevanwanzeeleKF Jan 16, 2026
7a79a12
Update nexus-certificate-manager-caplugin/NexusCertManagerCAPlugin.cs
joevanwanzeeleKF Jan 16, 2026
abf0b2b
added check for partial sync
joevanwanzeeleKF Jan 16, 2026
80a869c
added check for partial sync
joevanwanzeeleKF Jan 16, 2026
ad20419
Update nexus-certificate-manager-caplugin/NexusCertManagerCAPlugin.cs
joevanwanzeeleKF Jan 16, 2026
a6ad3c4
updating manifest for doctool build
joevanwanzeeleKF Jan 16, 2026
d7afff7
Merge branch 'initial_AB#64146' of https://github.com/Keyfactor/nexus…
joevanwanzeeleKF Jan 16, 2026
1927740
added configuration.md to solution
joevanwanzeeleKF Jan 28, 2026
c55e995
updated gitignore
joevanwanzeeleKF May 14, 2026
1f51509
Now returning Nexus CM processes as product ID's; implemented conditi…
joevanwanzeeleKF May 14, 2026
f2f7768
Update generated docs
May 14, 2026
e6b26ed
Merge branch 'release-1.1' into add_procedures_AB#81415
joevanwanzeeleKF May 15, 2026
91bc7cd
Change starter workflow version and update secrets
joevanwanzeeleKF May 15, 2026
9a5bbf8
added .net6.0 build
joevanwanzeeleKF May 15, 2026
ee9828c
Merge branch 'add_procedures_AB#81415' of https://github.com/Keyfacto…
joevanwanzeeleKF May 15, 2026
da1650a
removed leftover comment from configuration.md
joevanwanzeeleKF May 18, 2026
3413c6f
Update generated docs
May 18, 2026
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
16 changes: 3 additions & 13 deletions .github/workflows/keyfactor-bootstrap-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,9 @@ on:

jobs:
call-starter-workflow:
uses: keyfactor/actions/.github/workflows/starter.yml@v4
permissions:
contents: write
with:
command_token_url: ${{ vars.COMMAND_TOKEN_URL }}
command_hostname: ${{ vars.COMMAND_HOSTNAME }}
command_base_api_path: ${{ vars.COMMAND_API_PATH }}
uses: keyfactor/actions/.github/workflows/starter.yml@v3
secrets:
token: ${{ github.token }}
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 }}
entra_username: ${{ secrets.DOCTOOL_ENTRA_USERNAME }}
entra_password: ${{ secrets.DOCTOOL_ENTRA_PASSWD }}
command_client_id: ${{ secrets.COMMAND_CLIENT_ID }}
command_client_secret: ${{ secrets.COMMAND_CLIENT_SECRET }}
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -347,3 +347,4 @@ healthchecksdb
/cert.pem
/cert.csr
.config/dotnet-tools.json
/.claude/agents
13 changes: 11 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
## 1.0.0
* Initial release
### 1.1.0
* **Procedures as ProductIDs** — `GetProductIds()` now returns Nexus CA token procedure names dynamically from the `/procedures` endpoint rather than a single hardcoded value. Each certificate template in Command should be configured with a procedure name as its ProductID.
* **Pagination** — `Synchronize` and `GetCertificateList` now page through results in batches of 500, resolving failures that occurred when the max returned records limit (that defaults to 500) was reached.
* **Conditional synchronization** — `Synchronize` throws `NotSupportedException` with a clear explanation when `SyncProcedureField` is not configured. When configured, sync reads the specified `ExtendedCertSearch` field from each certificate to resolve its ProductID. See documentation for CA-side requirements.
* **`GetSingleRecord` fix** — `ProductID` is now resolved from the configured `ExtendedCertSearch` field instead of incorrectly using `CertId`.
* **`ValidateProductInfo`** — now validates that `ProductID` is non-empty.
* **Fixed deadlock risk** — replaced `.Result` with `.GetAwaiter().GetResult()` in `GetProductIds()`.
* **General Cleanup** — corrected "retreived" / "retreive" in log messages throughout.

### 1.0.0
* Initial release
229 changes: 229 additions & 0 deletions NexusCertManagerCAPlugin.Tests/HelpersAndModelsTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// 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.Collections.Generic;
using FluentAssertions;
using Keyfactor.Extensions.CAPlugin.NexusCertManager.models;
using Keyfactor.PKI.Enums.EJBCA;
using Xunit;

namespace Keyfactor.Extensions.CAPlugin.NexusCertManager.Tests
{
/// <summary>
/// Tests for <see cref="Helpers.GetStatusCodeFromNexusCADescription"/>.
///
/// Covers all known Nexus status string mappings and the unknown-status fallback.
/// </summary>
public class StatusMappingTests
{
[Theory]
[InlineData("issued")]
[InlineData("approved")]
[InlineData("expired")]
[InlineData("active")]
public void GetStatusCode_ActiveStatuses_ReturnGenerated(string status)
{
Helpers.GetStatusCodeFromNexusCADescription(status)
.Should().Be((int)EndEntityStatus.GENERATED);
}

[Theory]
[InlineData("processing")]
[InlineData("reissue_pending")]
[InlineData("pending")]
[InlineData("waiting_pickup")]
[InlineData("needs_approval")]
public void GetStatusCode_PendingStatuses_ReturnExternalValidation(string status)
{
Helpers.GetStatusCodeFromNexusCADescription(status)
.Should().Be((int)EndEntityStatus.EXTERNALVALIDATION);
}

[Theory]
[InlineData("denied")]
[InlineData("rejected")]
[InlineData("canceled")]
public void GetStatusCode_FailureStatuses_ReturnFailed(string status)
{
Helpers.GetStatusCodeFromNexusCADescription(status)
.Should().Be((int)EndEntityStatus.FAILED);
}

[Fact]
public void GetStatusCode_Revoked_ReturnRevoked()
{
Helpers.GetStatusCodeFromNexusCADescription("revoked")
.Should().Be((int)EndEntityStatus.REVOKED);
}

[Theory]
[InlineData("unknown_value")]
[InlineData("")]
[InlineData(null)]
public void GetStatusCode_UnknownOrEmptyStatus_ReturnNew(string status)
{
Helpers.GetStatusCodeFromNexusCADescription(status)
.Should().Be((int)EndEntityStatus.NEW,
because: "unknown statuses should default to NEW for manual triage");
}
}

/// <summary>
/// Tests for <see cref="Helpers.GetRevocationReasonCodeFromNexusCADescription"/>.
///
/// Covers all known Nexus revocation reason strings and the unspecified fallback.
/// </summary>
public class RevocationReasonMappingTests
{
[Theory]
[InlineData("key compromise", RevocationReason.KeyCompromise)]
[InlineData("affiliation changed", RevocationReason.AffiliationChanged)]
[InlineData("superseded", RevocationReason.Superseded)]
[InlineData("cessation of operation", RevocationReason.CessationOfOperation)]
[InlineData("certificate hold", RevocationReason.CertificateHold)]
[InlineData("privilege withdrawn", RevocationReason.PrivilegeWithdrawn)]
public void GetRevocationReason_KnownReasons_ReturnCorrectCode(string reason, int expected)
{
Helpers.GetRevocationReasonCodeFromNexusCADescription(reason)
.Should().Be(expected);
}

[Theory]
[InlineData("KEY COMPROMISE")] // case insensitivity
[InlineData("Key Compromise")]
public void GetRevocationReason_CaseInsensitive(string reason)
{
Helpers.GetRevocationReasonCodeFromNexusCADescription(reason)
.Should().Be((int)RevocationReason.KeyCompromise);
}

[Theory]
[InlineData("unknown reason")]
[InlineData("")]
[InlineData(null)]
public void GetRevocationReason_UnknownOrEmpty_ReturnUnspecified(string reason)
{
Helpers.GetRevocationReasonCodeFromNexusCADescription(reason)
.Should().Be(RevocationReason.Unspecified);
}
}

/// <summary>
/// Tests for <see cref="Helpers.ParseSubject"/>.
///
/// Covers CN extraction from various subject formats.
/// </summary>
public class ParseSubjectTests
{
[Fact]
public void ParseSubject_SimpleCN_ExtractedCorrectly()
{
Helpers.ParseSubject("CN=test.example.com, O=Acme", "CN=")
.Should().Be("test.example.com");
}

[Fact]
public void ParseSubject_CNWithEscapedComma_PreservesComma()
{
// Escaped commas in the CN value should survive round-trip
Helpers.ParseSubject(@"CN=Last\, First, O=Acme", "CN=")
.Should().Be("Last, First");
}

[Fact]
public void ParseSubject_MissingRdn_ThrowsException()
{
var act = () => Helpers.ParseSubject("O=Acme, C=US", "CN=");
act.Should().Throw<System.Exception>().WithMessage("*CN=*");
}

[Fact]
public void ParseSubject_ExtractsOrgUnit()
{
Helpers.ParseSubject("CN=test, OU=Engineering, O=Acme", "OU=")
.Should().Be("Engineering");
}
}

/// <summary>
/// Tests for <see cref="CmApiException"/>.
///
/// Covers error code descriptions and exception message plumbing.
/// </summary>
public class CmApiExceptionTests
{
[Theory]
[InlineData(0, "Success")]
[InlineData(-1, "General error")]
[InlineData(-7, "Missing field")]
[InlineData(-8, "Encoding error")]
[InlineData(-12, "Not initialized")]
[InlineData(-14, "Bad field value")]
[InlineData(-15, "Privilege error")]
[InlineData(-17, "Bad signature")]
[InlineData(-18, "Connection error")]
[InlineData(-19, "Signature required")]
[InlineData(-40, "Too many requests")]
[InlineData(-999, "Unknown error")]
public void GetErrorDescription_KnownCodes_ReturnExpectedDescription(int code, string expected)
{
var ex = new CmApiException(code, "test");
ex.GetErrorDescription().Should().Be(expected);
}

[Fact]
public void CmApiException_MessageIsPreserved()
{
var ex = new CmApiException(-1, "Something went wrong");
ex.Message.Should().Be("Something went wrong");
ex.ErrorCode.Should().Be(-1);
}
}

/// <summary>
/// Tests for <see cref="ListCertificatesRequest"/> — the query parameter model
/// used to paginate and filter certificate list requests.
///
/// Covers that key pagination fields are set correctly when constructing requests.
/// </summary>
public class ListCertificatesRequestTests
{
[Fact]
public void ListCertificatesRequest_PaginationFields_SetCorrectly()
{
var req = new ListCertificatesRequest
{
SearchLimit = 500,
SearchOffset = 1000
};

req.SearchLimit.Should().Be(500);
req.SearchOffset.Should().Be(1000);
}

[Fact]
public void ListCertificatesRequest_ExtendedSearchFields_SetCorrectly()
{
var req = new ListCertificatesRequest { Field1 = "ProcA", Field3 = "Something" };

req.Field1.Should().Be("ProcA");
req.Field2.Should().BeNull();
req.Field3.Should().Be("Something");
}

[Fact]
public void ListCertificatesRequest_AllFieldsNullByDefault()
{
var req = new ListCertificatesRequest();

req.SearchLimit.Should().BeNull();
req.SearchOffset.Should().BeNull();
req.Field1.Should().BeNull();
req.OrderBy.Should().BeNull();
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>Keyfactor.Extensions.CAPlugin.NexusCertManager.Tests</RootNamespace>
<AssemblyName>NexusCertManagerCAPlugin.Tests</AssemblyName>
<ImplicitUsings>disable</ImplicitUsings>
<Nullable>disable</Nullable>
<IsPackable>false</IsPackable>
</PropertyGroup>

<ItemGroup>
<!-- Test framework -->
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0" />
<PackageReference Include="xunit" Version="2.7.0" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.7">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<!-- Mocking -->
<PackageReference Include="Moq" Version="4.20.70" />
<!-- HTTP mocking for NexusCertManagerClient tests -->
<PackageReference Include="RichardSzalay.MockHttp" Version="7.0.0" />
<!-- Assertions -->
<PackageReference Include="FluentAssertions" Version="6.12.0" />
<!-- Match main project dependencies -->
<PackageReference Include="Newtonsoft.Json" Version="13.0.4" />
<PackageReference Include="RestSharp" Version="112.1.0" />
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="9.0.10" />
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\nexus-certificate-manager-caplugin\NexusCertManagerCAPlugin.csproj" />
</ItemGroup>

</Project>
Loading
Loading