Skip to content

Merge 2.0.0 to main - #90

Merged
indrora merged 1 commit into
mainfrom
release-2.0
Jul 8, 2026
Merged

Merge 2.0.0 to main#90
indrora merged 1 commit into
mainfrom
release-2.0

Conversation

@indrora

@indrora indrora commented Jul 8, 2026

Copy link
Copy Markdown
Member

Merge release-2.0 to main - Automated PR

* feat: `x509certificate2` removal (#73) (#79)

* feat: `x509certificate2` removal (#71)

* Update generated docs

* chore(lint): Fix PR review lint.

* Update generated docs

* test: unit tests for SeparateChain/IncludeCertChain conflict resolution in JobBase

Adds StorePropertiesParsingTests covering the four flag combinations so that
the override logic (SeparateChain forced to false when IncludeCertChain=false)
is caught at the unit level, not only by integration tests.

* Update generated docs

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>

* refactor: extract service layer from monolithic JobBase

Break domain logic out of JobBase into focused, testable services:

- StoreConfigurationParser: parses CertificateStoreDetails.Properties JSON
  into a typed StoreConfiguration, eliminating dynamic dispatch
- StorePathResolver: resolves StorePath strings (namespace/secret-name)
  into structured PathResolutionResult for all store type patterns
- JobCertificateParser: extracts certificate/key/chain from
  ManagementJobConfiguration with explicit format detection
- PasswordResolver: resolves passwords from inline values or K8S secret
  references, centralising the "buddy password" pattern
- CertificateChainExtractor: parses PEM chains into leaf + intermediates,
  handling both bundled and pre-separated chain formats
- KeystoreOperations: JKS/PKCS12 read/write operations moved out of
  handlers into a standalone service

None of these services require a Kubernetes client, making them fully
unit-testable without network access.

* refactor: introduce handler strategy pattern for secret operations

Replace inline switch/if chains in JobBase with a proper Strategy pattern:

- ISecretHandler: contract for Inventory, Management, Discovery, and
  Reenrollment operations on a specific secret/store type
- SecretHandlerBase: shared infrastructure (client, logging, result helpers)
- SecretHandlerFactory: creates the correct handler from SecretType enum
- Per-type handlers: TlsSecretHandler, OpaqueSecretHandler,
  JksSecretHandler, Pkcs12SecretHandler, ClusterSecretHandler,
  NamespaceSecretHandler, CertificateSecretHandler (read-only)

Supporting additions:
- SecretTypes enum: typed representation of Kubernetes secret types with
  normalisation and IsTlsType/IsOpaqueType helpers
- K8SJobCertificate model: replaces ad-hoc certificate data passing
- Exceptions: StoreNotFoundException, InvalidK8SSecretException,
  JkSisPkcs12Exception — typed errors replace bare Exception throws
- ICertificateStoreSerializer + JKS/PKCS12 serializer implementations
  moved from StoreTypes/ to Serializers/ (interface renamed for clarity)

* refactor: split monolithic KubeClient into focused client components

KubeClient.cs was a 3000+ line file mixing authentication, kubeconfig
parsing, secret CRUD, and CSR operations. Split into:

- KubeconfigParser: parses kubeconfig JSON into typed configuration,
  validates required fields, provides clear error messages
- SecretOperations: Kubernetes secret CRUD (create, read, update, delete,
  list) with retry logic and structured logging
- CertificateOperations: CSR-specific operations (list, read, approve,
  inject certificate status)
- KubeClient (KubeCertificateManagerClient): now a thin coordinator
  that initialises the authenticated client and delegates to the above

Also removes unreachable code branches, converts string interpolation
log calls to structured logging throughout, and adds retry logic with
configurable backoff.

* refactor: restructure job classes by store type, remove X509Certificate2

Job structure (flat → per-store-type):
- Remove Jobs/Inventory.cs, Management.cs, Discovery.cs, Reenrollment.cs
  (monolithic files with large switch statements on store type)
- Add Jobs/Base/: K8SJobBase, InventoryBase, ManagementBase, DiscoveryBase,
  ReenrollmentBase — shared logic each job type delegates to its handler
- Add Jobs/StoreTypes/<Type>/: one class per operation per store type
  (7 store types × up to 4 operations = 26 concrete job classes)
- manifest.json updated to route each capability to its dedicated class

X509Certificate2 removal:
- Replace X509Certificate2 usage throughout with BouncyCastle types
- K8SCertificateContext replaces X509Certificate2-based SerializedStoreInfo
- LoggingUtilities updated: GetCertificateSummary now accepts BouncyCastle
  X509Certificate; RedactPassword no longer leaks password length

Version logging:
- JobBase reads AssemblyInformationalVersionAttribute at startup and logs
  "K8S Orchestrator Extension version: {Version}" on every job execution
  (baked in at build time by GitHub Actions via -p:Version=<tag>)

Also removes TestConsole (superseded by integration test suite) and
store_types.json (superseded by integration-manifest.json).

* feat: add CachedCertificateProvider and comprehensive test suite

Test infrastructure:
- CachedCertificateProvider: thread-safe cache for generated certificates;
  eliminates redundant RSA key generation across test collections (RSA 8192
  takes 30+ seconds per key — this alone cut full-suite runtime by ~60%)
- IntegrationTestFixture: shared kubeconfig loading, K8S client creation,
  namespace setup/teardown for all integration test collections
- SkipUnless attribute: skips integration tests when RUN_INTEGRATION_TESTS
  is not set, keeping unit test runs fast

New unit tests (zero network access):
- Services: StoreConfigurationParser, StorePathResolver, PasswordResolver,
  CertificateChainExtractor, JobCertificateParser, KeystoreOperations
- Handlers: SecretHandlerBase, SecretHandlerFactory, all handler types
  (no-network paths), alias routing regression
- Clients: KubeconfigParser, SecretOperations, CertificateOperations,
  KubeCertificateManagerClient
- Jobs: ManagementBase, DiscoveryBase, PAMUtilities, exception paths,
  K8SJobCertificate, K8SCertificateContext
- Utilities: LoggingUtilities (60 cases including DoesNotRevealLength),
  CertificateUtilities, LoggingSafetyTests
- Enums: SecretTypes

Updated integration tests: migrated all 7 store-type integration test
files to use IntegrationTestFixture and new job class namespaces.

Also adds scripts/analyze-coverage.py for coverage gap analysis.

* docs: update CHANGELOG, ARCHITECTURE.md, Development.md, README for v2.0.0

- CHANGELOG.md: document v2.0.0 breaking changes — new store type routing
  via per-store-type job classes, removed X509Certificate2 dependency,
  updated job configuration model
- docs/ARCHITECTURE.md: new file documenting the service/handler/job
  architecture, authentication flow, and extension points
- Development.md: updated testing guide with CachedCertificateProvider
  guidance, integration test setup, coverage targets
- README.md: regenerated from docsource/ with updated store type dialogs
- docsource/: updated content and added SVG store type dialog images for
  all 7 store types
- .github/workflows: add test-doctool workflow, update starter workflow
- scripts/store_types/: updated kfutil helper scripts
- terraform/: add Terraform module examples for all store types

* docs(architecture): remove incorrect reenrollment references

Reenrollment is not a supported operation. Remove it from the overview
sentence, fix the store type operations table (K8SJKS and K8SPKCS12 were
incorrectly listed as 'All + Reenrollment'), and remove ReenrollmentBase.cs
from the base class directory listing.

* docs: auto-generate README and documentation [skip ci]

* docs: update compatibility to include Command 24.x and 25.x

Update the compatibility statement and UO version matrix to explicitly
call out support for Keyfactor Command platform versions 24.x and 25.x,
and add a net10.0 row for Command 25.x and newer.

* docs: auto-generate README and documentation [skip ci]

* docs: call out .NET 8 and .NET 10 compatibility in README

Add explicit mention of net8.0/net10.0 dual-targeting to the
Compatibility section so users know which build to download without
having to dig into the installation table.

* docs: auto-generate README and documentation [skip ci]

* docs(changelog): add v2.0.0 entry

* docs(changelog): merge pre-rebase content into v2.0.0 and 1.3.0 entries

Add missing breaking changes (JobBase dead property removal, KeystoreManager
removal), terraform feature, and richer 1.3.0 bug fixes (create-if-missing,
buddy-secret password, alias routing) and refactor/test chores from the
break/major_refactor branch changelog.

* fix: add missing Serializers directory to fix build

The Serializers/ directory containing JKS and PKCS12 store serializers
was never committed, causing build failures when handler files attempted
to reference the Keyfactor.Extensions.Orchestrator.K8S.Serializers namespace.

* docs(auth): add client certificate auth as alternative to SA token

- Fix fragile grep/awk token lookup in get_service_account_creds.sh and
  create_service_account.sh — now uses direct jsonpath lookup with a
  clear error message if the token Secret is missing (k8s v1.22+)
- Add generate_client_cert_creds.sh: end-to-end script that applies RBAC,
  generates an RSA key, submits and approves a k8s CSR, and builds a
  client-cert kubeconfig in one step
- Add kubernetes_svc_account_cert_auth.yaml: ClusterRole + ClusterRoleBinding
  for cert-based auth (kind: User subject, no ServiceAccount required)
- Add example_kubeconfig_cert.json showing client-certificate-data layout
- Rewrite scripts/kubernetes/README.md to present both auth options equally
  with comparison table, quickstart, config reference, and manual steps
- Update docsource/content.md Requirements section to document both methods

* docs: auto-generate README and documentation [skip ci]

* feat(auth): add in-cluster pod identity as third authentication option

Plugin changes:
- KubeClient.GetKubeClient(): detect KUBERNETES_SERVICE_HOST and call
  InClusterConfig() when no kubeconfig is provided, using the projected
  service account token mounted by kubelet (auto-rotated every hour)
- JobBase.InitializeProperties(): allow empty KubeSvcCreds when running
  in-cluster instead of throwing ConfigurationException

Scripts/docs:
- Add keyfactor-orchestrator-deployment.yaml: Deployment manifest that
  runs the UO as a pod using the keyfactor-orchestrator-sa ServiceAccount
- Update scripts/kubernetes/README.md: add Option 3 to comparison table
  and full setup section (apply SA YAML, deploy, leave Server Password blank)
- Update docsource/content.md: document all three auth options equally

* docs: auto-generate README and documentation [skip ci]

* docs(auth): clarify in-cluster requires "No value" for Server Password in Command UI

* docs: auto-generate README and documentation [skip ci]

* fix(security): SOX/SOC2 compliance remediations and UseSSL bug fix

Compliance remediations (all findings were pre-existing on branch):
- Redact certificate bytes in UpdateOpaqueSecret log traces (CRIT-1)
- Log CSR certificate length only, not content preview (CRIT-2)
- Add structured AUDIT log entries (store_access, secret_read/write/delete)
  to ManagementBase, InventoryBase, DiscoveryBase, SecretOperations (CRIT-3)
- ValidateK8SName throws ArgumentException instead of warning; 5+ segment
  paths return Success=false and fail the job (CRIT-4)
- Zero KubeSvcCreds and ServerPassword after KubeClient construction (HIGH-1)
- RedactKubeconfig validates JSON structure before applying label; non-JSON
  returns POSSIBLY_MALFORMED_CREDENTIAL (HIGH-2)
- Silent catch blocks in JKS/PKCS12 serializers now log exception type (HIGH-3)
- PAM resolution outcome promoted from LogTrace to LogInformation (HIGH-4)
- TLS skip override promoted from LogWarning to LogError with structured
  SECURITY_CONFIG_OVERRIDE field (HIGH-5)
- ReadBuddyPass: make passwordSecretName discard explicit with _ (HIGH-6)
- HandleRemove returns Warning (not Success) when store not found so job
  history distinguishes no-op from actual removal (HIGH-7)
- Remove KubeSvcCreds from storeProperties dict after client construction (MED-1)
- StorePathResolver rejects 5+ segment paths (MED-4)
- Handler NotFound catch blocks use HttpOperationException status code
  comparison instead of ex.Message string matching (MED-5)
- Discovery InitializeStore wrapped in try/catch matching Inventory/Management
  pattern (MED-6)

Bug fix:
- UseSSL value from job config (config.UseSSL) was never forwarded to
  KubeCertificateManagerClient — TLS verification was always defaulting to
  true regardless of the store's Use SSL checkbox. Now captured in each
  InitializeStore overload and passed through InitializeKubeClient.

* security: remove GetPasswordCorrelationId and update changelog

Removes SHA-256 password correlation ID (MED-2) — low-entropy passwords
are reversible via dictionary attack and RedactPassword is already present
at all call sites. Updates CHANGELOG.md with all v2.0.0 changes from this
session including client cert auth, in-cluster auth, UseSSL fix, audit
logging, and compliance remediations.

* docs: remove duplicate content sections from generated README

Regenerated with fixed doctooldotnet (Keyfactor/doctooldotnet#9).
Named content.md sections were being emitted twice due to title mutation
before the custom-sections filter ran.

NOTE: Actions will revert this until doctooldotnet PR #9 is merged.

* docs: auto-generate README and documentation [skip ci]

* chore(ci): revert to old doctool

* fix(k8scert): ignore storepath for csr mode and add regression coverage

* fix(inventory): sanitize URL cluster names in discovery location strings (#88)

---------

Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Copilot AI review requested due to automatic review settings July 8, 2026 22:49
// Verify the deployed certificate matches the input certificate
Assert.True(secret.Data.ContainsKey("tls.crt"), "Secret should have tls.crt field");
var deployedCertPem = Encoding.UTF8.GetString(secret.Data["tls.crt"]);
var parser = new Org.BouncyCastle.X509.X509CertificateParser();
// Verify the deployed certificate matches the input certificate
Assert.True(secret.Data.ContainsKey("tls.crt"), "Secret should have tls.crt field");
var deployedCertPem = Encoding.UTF8.GetString(secret.Data["tls.crt"]);
var parser = new Org.BouncyCastle.X509.X509CertificateParser();
// Get certificate bytes for the serializer
// Use PKCS12 if available (for certificates with private keys), otherwise use raw cert bytes
// (for certificate-only entries like trusted CA certs)
byte[] newCertBytes = certObj.Pkcs12 ?? certObj.CertBytes;
try
{
await _k8sClient.CoreV1.DeleteNamespacedSecretAsync(secretName, ns);
await _k8sClient.CoreV1.DeleteCollectionNamespacedSecretAsync(
}

return result;
return result!;
@indrora
indrora merged commit 74aa37a into main Jul 8, 2026
10 checks passed

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot wasn't able to review this pull request because it exceeds the maximum number of lines (20,000). Try reducing the number of changed lines and requesting a review from Copilot again.

spbsoluble added a commit that referenced this pull request Aug 13, 2026
* feat: `x509certificate2` removal (#73) (#79)

* feat: `x509certificate2` removal (#71)

* Update generated docs

* chore(lint): Fix PR review lint.

* Update generated docs

* test: unit tests for SeparateChain/IncludeCertChain conflict resolution in JobBase

Adds StorePropertiesParsingTests covering the four flag combinations so that
the override logic (SeparateChain forced to false when IncludeCertChain=false)
is caught at the unit level, not only by integration tests.

* Update generated docs

---------




* refactor: extract service layer from monolithic JobBase

Break domain logic out of JobBase into focused, testable services:

- StoreConfigurationParser: parses CertificateStoreDetails.Properties JSON
  into a typed StoreConfiguration, eliminating dynamic dispatch
- StorePathResolver: resolves StorePath strings (namespace/secret-name)
  into structured PathResolutionResult for all store type patterns
- JobCertificateParser: extracts certificate/key/chain from
  ManagementJobConfiguration with explicit format detection
- PasswordResolver: resolves passwords from inline values or K8S secret
  references, centralising the "buddy password" pattern
- CertificateChainExtractor: parses PEM chains into leaf + intermediates,
  handling both bundled and pre-separated chain formats
- KeystoreOperations: JKS/PKCS12 read/write operations moved out of
  handlers into a standalone service

None of these services require a Kubernetes client, making them fully
unit-testable without network access.

* refactor: introduce handler strategy pattern for secret operations

Replace inline switch/if chains in JobBase with a proper Strategy pattern:

- ISecretHandler: contract for Inventory, Management, Discovery, and
  Reenrollment operations on a specific secret/store type
- SecretHandlerBase: shared infrastructure (client, logging, result helpers)
- SecretHandlerFactory: creates the correct handler from SecretType enum
- Per-type handlers: TlsSecretHandler, OpaqueSecretHandler,
  JksSecretHandler, Pkcs12SecretHandler, ClusterSecretHandler,
  NamespaceSecretHandler, CertificateSecretHandler (read-only)

Supporting additions:
- SecretTypes enum: typed representation of Kubernetes secret types with
  normalisation and IsTlsType/IsOpaqueType helpers
- K8SJobCertificate model: replaces ad-hoc certificate data passing
- Exceptions: StoreNotFoundException, InvalidK8SSecretException,
  JkSisPkcs12Exception — typed errors replace bare Exception throws
- ICertificateStoreSerializer + JKS/PKCS12 serializer implementations
  moved from StoreTypes/ to Serializers/ (interface renamed for clarity)

* refactor: split monolithic KubeClient into focused client components

KubeClient.cs was a 3000+ line file mixing authentication, kubeconfig
parsing, secret CRUD, and CSR operations. Split into:

- KubeconfigParser: parses kubeconfig JSON into typed configuration,
  validates required fields, provides clear error messages
- SecretOperations: Kubernetes secret CRUD (create, read, update, delete,
  list) with retry logic and structured logging
- CertificateOperations: CSR-specific operations (list, read, approve,
  inject certificate status)
- KubeClient (KubeCertificateManagerClient): now a thin coordinator
  that initialises the authenticated client and delegates to the above

Also removes unreachable code branches, converts string interpolation
log calls to structured logging throughout, and adds retry logic with
configurable backoff.

* refactor: restructure job classes by store type, remove X509Certificate2

Job structure (flat → per-store-type):
- Remove Jobs/Inventory.cs, Management.cs, Discovery.cs, Reenrollment.cs
  (monolithic files with large switch statements on store type)
- Add Jobs/Base/: K8SJobBase, InventoryBase, ManagementBase, DiscoveryBase,
  ReenrollmentBase — shared logic each job type delegates to its handler
- Add Jobs/StoreTypes/<Type>/: one class per operation per store type
  (7 store types × up to 4 operations = 26 concrete job classes)
- manifest.json updated to route each capability to its dedicated class

X509Certificate2 removal:
- Replace X509Certificate2 usage throughout with BouncyCastle types
- K8SCertificateContext replaces X509Certificate2-based SerializedStoreInfo
- LoggingUtilities updated: GetCertificateSummary now accepts BouncyCastle
  X509Certificate; RedactPassword no longer leaks password length

Version logging:
- JobBase reads AssemblyInformationalVersionAttribute at startup and logs
  "K8S Orchestrator Extension version: {Version}" on every job execution
  (baked in at build time by GitHub Actions via -p:Version=<tag>)

Also removes TestConsole (superseded by integration test suite) and
store_types.json (superseded by integration-manifest.json).

* feat: add CachedCertificateProvider and comprehensive test suite

Test infrastructure:
- CachedCertificateProvider: thread-safe cache for generated certificates;
  eliminates redundant RSA key generation across test collections (RSA 8192
  takes 30+ seconds per key — this alone cut full-suite runtime by ~60%)
- IntegrationTestFixture: shared kubeconfig loading, K8S client creation,
  namespace setup/teardown for all integration test collections
- SkipUnless attribute: skips integration tests when RUN_INTEGRATION_TESTS
  is not set, keeping unit test runs fast

New unit tests (zero network access):
- Services: StoreConfigurationParser, StorePathResolver, PasswordResolver,
  CertificateChainExtractor, JobCertificateParser, KeystoreOperations
- Handlers: SecretHandlerBase, SecretHandlerFactory, all handler types
  (no-network paths), alias routing regression
- Clients: KubeconfigParser, SecretOperations, CertificateOperations,
  KubeCertificateManagerClient
- Jobs: ManagementBase, DiscoveryBase, PAMUtilities, exception paths,
  K8SJobCertificate, K8SCertificateContext
- Utilities: LoggingUtilities (60 cases including DoesNotRevealLength),
  CertificateUtilities, LoggingSafetyTests
- Enums: SecretTypes

Updated integration tests: migrated all 7 store-type integration test
files to use IntegrationTestFixture and new job class namespaces.

Also adds scripts/analyze-coverage.py for coverage gap analysis.

* docs: update CHANGELOG, ARCHITECTURE.md, Development.md, README for v2.0.0

- CHANGELOG.md: document v2.0.0 breaking changes — new store type routing
  via per-store-type job classes, removed X509Certificate2 dependency,
  updated job configuration model
- docs/ARCHITECTURE.md: new file documenting the service/handler/job
  architecture, authentication flow, and extension points
- Development.md: updated testing guide with CachedCertificateProvider
  guidance, integration test setup, coverage targets
- README.md: regenerated from docsource/ with updated store type dialogs
- docsource/: updated content and added SVG store type dialog images for
  all 7 store types
- .github/workflows: add test-doctool workflow, update starter workflow
- scripts/store_types/: updated kfutil helper scripts
- terraform/: add Terraform module examples for all store types

* docs(architecture): remove incorrect reenrollment references

Reenrollment is not a supported operation. Remove it from the overview
sentence, fix the store type operations table (K8SJKS and K8SPKCS12 were
incorrectly listed as 'All + Reenrollment'), and remove ReenrollmentBase.cs
from the base class directory listing.

* docs: auto-generate README and documentation [skip ci]

* docs: update compatibility to include Command 24.x and 25.x

Update the compatibility statement and UO version matrix to explicitly
call out support for Keyfactor Command platform versions 24.x and 25.x,
and add a net10.0 row for Command 25.x and newer.

* docs: auto-generate README and documentation [skip ci]

* docs: call out .NET 8 and .NET 10 compatibility in README

Add explicit mention of net8.0/net10.0 dual-targeting to the
Compatibility section so users know which build to download without
having to dig into the installation table.

* docs: auto-generate README and documentation [skip ci]

* docs(changelog): add v2.0.0 entry

* docs(changelog): merge pre-rebase content into v2.0.0 and 1.3.0 entries

Add missing breaking changes (JobBase dead property removal, KeystoreManager
removal), terraform feature, and richer 1.3.0 bug fixes (create-if-missing,
buddy-secret password, alias routing) and refactor/test chores from the
break/major_refactor branch changelog.

* fix: add missing Serializers directory to fix build

The Serializers/ directory containing JKS and PKCS12 store serializers
was never committed, causing build failures when handler files attempted
to reference the Keyfactor.Extensions.Orchestrator.K8S.Serializers namespace.

* docs(auth): add client certificate auth as alternative to SA token

- Fix fragile grep/awk token lookup in get_service_account_creds.sh and
  create_service_account.sh — now uses direct jsonpath lookup with a
  clear error message if the token Secret is missing (k8s v1.22+)
- Add generate_client_cert_creds.sh: end-to-end script that applies RBAC,
  generates an RSA key, submits and approves a k8s CSR, and builds a
  client-cert kubeconfig in one step
- Add kubernetes_svc_account_cert_auth.yaml: ClusterRole + ClusterRoleBinding
  for cert-based auth (kind: User subject, no ServiceAccount required)
- Add example_kubeconfig_cert.json showing client-certificate-data layout
- Rewrite scripts/kubernetes/README.md to present both auth options equally
  with comparison table, quickstart, config reference, and manual steps
- Update docsource/content.md Requirements section to document both methods

* docs: auto-generate README and documentation [skip ci]

* feat(auth): add in-cluster pod identity as third authentication option

Plugin changes:
- KubeClient.GetKubeClient(): detect KUBERNETES_SERVICE_HOST and call
  InClusterConfig() when no kubeconfig is provided, using the projected
  service account token mounted by kubelet (auto-rotated every hour)
- JobBase.InitializeProperties(): allow empty KubeSvcCreds when running
  in-cluster instead of throwing ConfigurationException

Scripts/docs:
- Add keyfactor-orchestrator-deployment.yaml: Deployment manifest that
  runs the UO as a pod using the keyfactor-orchestrator-sa ServiceAccount
- Update scripts/kubernetes/README.md: add Option 3 to comparison table
  and full setup section (apply SA YAML, deploy, leave Server Password blank)
- Update docsource/content.md: document all three auth options equally

* docs: auto-generate README and documentation [skip ci]

* docs(auth): clarify in-cluster requires "No value" for Server Password in Command UI

* docs: auto-generate README and documentation [skip ci]

* fix(security): SOX/SOC2 compliance remediations and UseSSL bug fix

Compliance remediations (all findings were pre-existing on branch):
- Redact certificate bytes in UpdateOpaqueSecret log traces (CRIT-1)
- Log CSR certificate length only, not content preview (CRIT-2)
- Add structured AUDIT log entries (store_access, secret_read/write/delete)
  to ManagementBase, InventoryBase, DiscoveryBase, SecretOperations (CRIT-3)
- ValidateK8SName throws ArgumentException instead of warning; 5+ segment
  paths return Success=false and fail the job (CRIT-4)
- Zero KubeSvcCreds and ServerPassword after KubeClient construction (HIGH-1)
- RedactKubeconfig validates JSON structure before applying label; non-JSON
  returns POSSIBLY_MALFORMED_CREDENTIAL (HIGH-2)
- Silent catch blocks in JKS/PKCS12 serializers now log exception type (HIGH-3)
- PAM resolution outcome promoted from LogTrace to LogInformation (HIGH-4)
- TLS skip override promoted from LogWarning to LogError with structured
  SECURITY_CONFIG_OVERRIDE field (HIGH-5)
- ReadBuddyPass: make passwordSecretName discard explicit with _ (HIGH-6)
- HandleRemove returns Warning (not Success) when store not found so job
  history distinguishes no-op from actual removal (HIGH-7)
- Remove KubeSvcCreds from storeProperties dict after client construction (MED-1)
- StorePathResolver rejects 5+ segment paths (MED-4)
- Handler NotFound catch blocks use HttpOperationException status code
  comparison instead of ex.Message string matching (MED-5)
- Discovery InitializeStore wrapped in try/catch matching Inventory/Management
  pattern (MED-6)

Bug fix:
- UseSSL value from job config (config.UseSSL) was never forwarded to
  KubeCertificateManagerClient — TLS verification was always defaulting to
  true regardless of the store's Use SSL checkbox. Now captured in each
  InitializeStore overload and passed through InitializeKubeClient.

* security: remove GetPasswordCorrelationId and update changelog

Removes SHA-256 password correlation ID (MED-2) — low-entropy passwords
are reversible via dictionary attack and RedactPassword is already present
at all call sites. Updates CHANGELOG.md with all v2.0.0 changes from this
session including client cert auth, in-cluster auth, UseSSL fix, audit
logging, and compliance remediations.

* docs: remove duplicate content sections from generated README

Regenerated with fixed doctooldotnet (Keyfactor/doctooldotnet#9).
Named content.md sections were being emitted twice due to title mutation
before the custom-sections filter ran.

NOTE: Actions will revert this until doctooldotnet PR #9 is merged.

* docs: auto-generate README and documentation [skip ci]

* chore(ci): revert to old doctool

* fix(k8scert): ignore storepath for csr mode and add regression coverage

* fix(inventory): sanitize URL cluster names in discovery location strings (#88)

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
indrora added a commit that referenced this pull request Aug 26, 2026
…es (#92)

* feat: Major refactor for v2.0.0 (#85) (#90)

* feat: `x509certificate2` removal (#73) (#79)

* feat: `x509certificate2` removal (#71)

* Update generated docs

* chore(lint): Fix PR review lint.

* Update generated docs

* test: unit tests for SeparateChain/IncludeCertChain conflict resolution in JobBase

Adds StorePropertiesParsingTests covering the four flag combinations so that
the override logic (SeparateChain forced to false when IncludeCertChain=false)
is caught at the unit level, not only by integration tests.

* Update generated docs

---------




* refactor: extract service layer from monolithic JobBase

Break domain logic out of JobBase into focused, testable services:

- StoreConfigurationParser: parses CertificateStoreDetails.Properties JSON
  into a typed StoreConfiguration, eliminating dynamic dispatch
- StorePathResolver: resolves StorePath strings (namespace/secret-name)
  into structured PathResolutionResult for all store type patterns
- JobCertificateParser: extracts certificate/key/chain from
  ManagementJobConfiguration with explicit format detection
- PasswordResolver: resolves passwords from inline values or K8S secret
  references, centralising the "buddy password" pattern
- CertificateChainExtractor: parses PEM chains into leaf + intermediates,
  handling both bundled and pre-separated chain formats
- KeystoreOperations: JKS/PKCS12 read/write operations moved out of
  handlers into a standalone service

None of these services require a Kubernetes client, making them fully
unit-testable without network access.

* refactor: introduce handler strategy pattern for secret operations

Replace inline switch/if chains in JobBase with a proper Strategy pattern:

- ISecretHandler: contract for Inventory, Management, Discovery, and
  Reenrollment operations on a specific secret/store type
- SecretHandlerBase: shared infrastructure (client, logging, result helpers)
- SecretHandlerFactory: creates the correct handler from SecretType enum
- Per-type handlers: TlsSecretHandler, OpaqueSecretHandler,
  JksSecretHandler, Pkcs12SecretHandler, ClusterSecretHandler,
  NamespaceSecretHandler, CertificateSecretHandler (read-only)

Supporting additions:
- SecretTypes enum: typed representation of Kubernetes secret types with
  normalisation and IsTlsType/IsOpaqueType helpers
- K8SJobCertificate model: replaces ad-hoc certificate data passing
- Exceptions: StoreNotFoundException, InvalidK8SSecretException,
  JkSisPkcs12Exception — typed errors replace bare Exception throws
- ICertificateStoreSerializer + JKS/PKCS12 serializer implementations
  moved from StoreTypes/ to Serializers/ (interface renamed for clarity)

* refactor: split monolithic KubeClient into focused client components

KubeClient.cs was a 3000+ line file mixing authentication, kubeconfig
parsing, secret CRUD, and CSR operations. Split into:

- KubeconfigParser: parses kubeconfig JSON into typed configuration,
  validates required fields, provides clear error messages
- SecretOperations: Kubernetes secret CRUD (create, read, update, delete,
  list) with retry logic and structured logging
- CertificateOperations: CSR-specific operations (list, read, approve,
  inject certificate status)
- KubeClient (KubeCertificateManagerClient): now a thin coordinator
  that initialises the authenticated client and delegates to the above

Also removes unreachable code branches, converts string interpolation
log calls to structured logging throughout, and adds retry logic with
configurable backoff.

* refactor: restructure job classes by store type, remove X509Certificate2

Job structure (flat → per-store-type):
- Remove Jobs/Inventory.cs, Management.cs, Discovery.cs, Reenrollment.cs
  (monolithic files with large switch statements on store type)
- Add Jobs/Base/: K8SJobBase, InventoryBase, ManagementBase, DiscoveryBase,
  ReenrollmentBase — shared logic each job type delegates to its handler
- Add Jobs/StoreTypes/<Type>/: one class per operation per store type
  (7 store types × up to 4 operations = 26 concrete job classes)
- manifest.json updated to route each capability to its dedicated class

X509Certificate2 removal:
- Replace X509Certificate2 usage throughout with BouncyCastle types
- K8SCertificateContext replaces X509Certificate2-based SerializedStoreInfo
- LoggingUtilities updated: GetCertificateSummary now accepts BouncyCastle
  X509Certificate; RedactPassword no longer leaks password length

Version logging:
- JobBase reads AssemblyInformationalVersionAttribute at startup and logs
  "K8S Orchestrator Extension version: {Version}" on every job execution
  (baked in at build time by GitHub Actions via -p:Version=<tag>)

Also removes TestConsole (superseded by integration test suite) and
store_types.json (superseded by integration-manifest.json).

* feat: add CachedCertificateProvider and comprehensive test suite

Test infrastructure:
- CachedCertificateProvider: thread-safe cache for generated certificates;
  eliminates redundant RSA key generation across test collections (RSA 8192
  takes 30+ seconds per key — this alone cut full-suite runtime by ~60%)
- IntegrationTestFixture: shared kubeconfig loading, K8S client creation,
  namespace setup/teardown for all integration test collections
- SkipUnless attribute: skips integration tests when RUN_INTEGRATION_TESTS
  is not set, keeping unit test runs fast

New unit tests (zero network access):
- Services: StoreConfigurationParser, StorePathResolver, PasswordResolver,
  CertificateChainExtractor, JobCertificateParser, KeystoreOperations
- Handlers: SecretHandlerBase, SecretHandlerFactory, all handler types
  (no-network paths), alias routing regression
- Clients: KubeconfigParser, SecretOperations, CertificateOperations,
  KubeCertificateManagerClient
- Jobs: ManagementBase, DiscoveryBase, PAMUtilities, exception paths,
  K8SJobCertificate, K8SCertificateContext
- Utilities: LoggingUtilities (60 cases including DoesNotRevealLength),
  CertificateUtilities, LoggingSafetyTests
- Enums: SecretTypes

Updated integration tests: migrated all 7 store-type integration test
files to use IntegrationTestFixture and new job class namespaces.

Also adds scripts/analyze-coverage.py for coverage gap analysis.

* docs: update CHANGELOG, ARCHITECTURE.md, Development.md, README for v2.0.0

- CHANGELOG.md: document v2.0.0 breaking changes — new store type routing
  via per-store-type job classes, removed X509Certificate2 dependency,
  updated job configuration model
- docs/ARCHITECTURE.md: new file documenting the service/handler/job
  architecture, authentication flow, and extension points
- Development.md: updated testing guide with CachedCertificateProvider
  guidance, integration test setup, coverage targets
- README.md: regenerated from docsource/ with updated store type dialogs
- docsource/: updated content and added SVG store type dialog images for
  all 7 store types
- .github/workflows: add test-doctool workflow, update starter workflow
- scripts/store_types/: updated kfutil helper scripts
- terraform/: add Terraform module examples for all store types

* docs(architecture): remove incorrect reenrollment references

Reenrollment is not a supported operation. Remove it from the overview
sentence, fix the store type operations table (K8SJKS and K8SPKCS12 were
incorrectly listed as 'All + Reenrollment'), and remove ReenrollmentBase.cs
from the base class directory listing.

* docs: auto-generate README and documentation [skip ci]

* docs: update compatibility to include Command 24.x and 25.x

Update the compatibility statement and UO version matrix to explicitly
call out support for Keyfactor Command platform versions 24.x and 25.x,
and add a net10.0 row for Command 25.x and newer.

* docs: auto-generate README and documentation [skip ci]

* docs: call out .NET 8 and .NET 10 compatibility in README

Add explicit mention of net8.0/net10.0 dual-targeting to the
Compatibility section so users know which build to download without
having to dig into the installation table.

* docs: auto-generate README and documentation [skip ci]

* docs(changelog): add v2.0.0 entry

* docs(changelog): merge pre-rebase content into v2.0.0 and 1.3.0 entries

Add missing breaking changes (JobBase dead property removal, KeystoreManager
removal), terraform feature, and richer 1.3.0 bug fixes (create-if-missing,
buddy-secret password, alias routing) and refactor/test chores from the
break/major_refactor branch changelog.

* fix: add missing Serializers directory to fix build

The Serializers/ directory containing JKS and PKCS12 store serializers
was never committed, causing build failures when handler files attempted
to reference the Keyfactor.Extensions.Orchestrator.K8S.Serializers namespace.

* docs(auth): add client certificate auth as alternative to SA token

- Fix fragile grep/awk token lookup in get_service_account_creds.sh and
  create_service_account.sh — now uses direct jsonpath lookup with a
  clear error message if the token Secret is missing (k8s v1.22+)
- Add generate_client_cert_creds.sh: end-to-end script that applies RBAC,
  generates an RSA key, submits and approves a k8s CSR, and builds a
  client-cert kubeconfig in one step
- Add kubernetes_svc_account_cert_auth.yaml: ClusterRole + ClusterRoleBinding
  for cert-based auth (kind: User subject, no ServiceAccount required)
- Add example_kubeconfig_cert.json showing client-certificate-data layout
- Rewrite scripts/kubernetes/README.md to present both auth options equally
  with comparison table, quickstart, config reference, and manual steps
- Update docsource/content.md Requirements section to document both methods

* docs: auto-generate README and documentation [skip ci]

* feat(auth): add in-cluster pod identity as third authentication option

Plugin changes:
- KubeClient.GetKubeClient(): detect KUBERNETES_SERVICE_HOST and call
  InClusterConfig() when no kubeconfig is provided, using the projected
  service account token mounted by kubelet (auto-rotated every hour)
- JobBase.InitializeProperties(): allow empty KubeSvcCreds when running
  in-cluster instead of throwing ConfigurationException

Scripts/docs:
- Add keyfactor-orchestrator-deployment.yaml: Deployment manifest that
  runs the UO as a pod using the keyfactor-orchestrator-sa ServiceAccount
- Update scripts/kubernetes/README.md: add Option 3 to comparison table
  and full setup section (apply SA YAML, deploy, leave Server Password blank)
- Update docsource/content.md: document all three auth options equally

* docs: auto-generate README and documentation [skip ci]

* docs(auth): clarify in-cluster requires "No value" for Server Password in Command UI

* docs: auto-generate README and documentation [skip ci]

* fix(security): SOX/SOC2 compliance remediations and UseSSL bug fix

Compliance remediations (all findings were pre-existing on branch):
- Redact certificate bytes in UpdateOpaqueSecret log traces (CRIT-1)
- Log CSR certificate length only, not content preview (CRIT-2)
- Add structured AUDIT log entries (store_access, secret_read/write/delete)
  to ManagementBase, InventoryBase, DiscoveryBase, SecretOperations (CRIT-3)
- ValidateK8SName throws ArgumentException instead of warning; 5+ segment
  paths return Success=false and fail the job (CRIT-4)
- Zero KubeSvcCreds and ServerPassword after KubeClient construction (HIGH-1)
- RedactKubeconfig validates JSON structure before applying label; non-JSON
  returns POSSIBLY_MALFORMED_CREDENTIAL (HIGH-2)
- Silent catch blocks in JKS/PKCS12 serializers now log exception type (HIGH-3)
- PAM resolution outcome promoted from LogTrace to LogInformation (HIGH-4)
- TLS skip override promoted from LogWarning to LogError with structured
  SECURITY_CONFIG_OVERRIDE field (HIGH-5)
- ReadBuddyPass: make passwordSecretName discard explicit with _ (HIGH-6)
- HandleRemove returns Warning (not Success) when store not found so job
  history distinguishes no-op from actual removal (HIGH-7)
- Remove KubeSvcCreds from storeProperties dict after client construction (MED-1)
- StorePathResolver rejects 5+ segment paths (MED-4)
- Handler NotFound catch blocks use HttpOperationException status code
  comparison instead of ex.Message string matching (MED-5)
- Discovery InitializeStore wrapped in try/catch matching Inventory/Management
  pattern (MED-6)

Bug fix:
- UseSSL value from job config (config.UseSSL) was never forwarded to
  KubeCertificateManagerClient — TLS verification was always defaulting to
  true regardless of the store's Use SSL checkbox. Now captured in each
  InitializeStore overload and passed through InitializeKubeClient.

* security: remove GetPasswordCorrelationId and update changelog

Removes SHA-256 password correlation ID (MED-2) — low-entropy passwords
are reversible via dictionary attack and RedactPassword is already present
at all call sites. Updates CHANGELOG.md with all v2.0.0 changes from this
session including client cert auth, in-cluster auth, UseSSL fix, audit
logging, and compliance remediations.

* docs: remove duplicate content sections from generated README

Regenerated with fixed doctooldotnet (Keyfactor/doctooldotnet#9).
Named content.md sections were being emitted twice due to title mutation
before the custom-sections filter ran.

NOTE: Actions will revert this until doctooldotnet PR #9 is merged.

* docs: auto-generate README and documentation [skip ci]

* chore(ci): revert to old doctool

* fix(k8scert): ignore storepath for csr mode and add regression coverage

* fix(inventory): sanitize URL cluster names in discovery location strings (#88)

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(management): stop reporting Success on silent secret write failures

ManagementBase.HandleAdd discarded the handler's returned V1Secret and
unconditionally returned Success, so a K8S write that failed without
throwing (e.g. a pre-existing secret update that silently no-opped) was
still reported to Command as a successful deployment. HandleAdd now
checks the returned V1Secret and fails the job with an actionable
message when it is null.

KubeCertificateManagerClient.CreateOrUpdateCertificateStoreSecret used
a blind create-then-catch flow, routing to the update path only on a
free-text e.Message.Contains("Conflict") match; any other
HttpOperationException (e.g. 403 from RBAC scoping) fell through and
returned null silently. It now reads the secret first (existence check
via SecretOperations.GetSecret, which only swallows a typed 404) and
branches to update when it already exists, matching the JKS/PKCS12
path. A typed HttpStatusCode.Conflict on the create call still falls
back to update for a genuine create race; every other
HttpOperationException now propagates instead of being swallowed.

Adds regression coverage for both fixes: ManagementBaseTests covers the
null-handler-result -> Failure path, and the new
KubeClientCreateOrUpdateSecretTests covers create/update routing plus
403 propagation on both the existence check and the create call.

Fixes #91

* fix(make): pin test-cluster-cleanup to the integration test kube context

test-cluster-cleanup used the current kubectl context, so with a different
active context (e.g. docker-desktop) it silently cleaned the wrong cluster
and stale test namespaces accumulated on kf-integrations for months —
making cluster-wide inventory integration tests pathologically slow.

Introduce TEST_KUBE_CONTEXT (default kf-integrations, overridable) and pass
--context to all kubectl calls in the cleanup target.

* test(k8scert): skip cluster-wide CSR inventory test on unpopulated clusters

Inventory_ClusterWideMode_InventoriesAllIssuedCsrs_InCurrentCluster hard-
asserted >=30 issued CSRs, which only holds on the populated lab cluster —
it can never pass on the ephemeral cluster CI provisions, and this PR's CI
run was the first to execute it there. Soft-skip when the cluster has no
issued CSRs; behavior against the lab cluster is unchanged (verified via
make test-store-cert, 10 passed on both TFMs).

* chore(deps): bump GitHub Actions versions

Incorporates the dependabot version bumps from PRs #80-84:
- actions/checkout v4 -> v6 (#80)
- actions/setup-dotnet v4 -> v5 (#81)
- softprops/action-gh-release v1 -> v2 (#82)
- actions/github-script v7 -> v8 (#83)
- actions/upload-artifact v4 -> v7 (#84)

* style(tests): use ternary in SetupRead per code-quality bot feedback on PR #92

Both branches assign to the same seq variable — a ternary expresses that
more directly than if/else.

---------

Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
indrora added a commit that referenced this pull request Aug 26, 2026
* feat: Major refactor for v2.0.0 (#85)

* feat: `x509certificate2` removal (#73) (#79)

* feat: `x509certificate2` removal (#71)

* Update generated docs

* chore(lint): Fix PR review lint.

* Update generated docs

* test: unit tests for SeparateChain/IncludeCertChain conflict resolution in JobBase

Adds StorePropertiesParsingTests covering the four flag combinations so that
the override logic (SeparateChain forced to false when IncludeCertChain=false)
is caught at the unit level, not only by integration tests.

* Update generated docs

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>

* refactor: extract service layer from monolithic JobBase

Break domain logic out of JobBase into focused, testable services:

- StoreConfigurationParser: parses CertificateStoreDetails.Properties JSON
  into a typed StoreConfiguration, eliminating dynamic dispatch
- StorePathResolver: resolves StorePath strings (namespace/secret-name)
  into structured PathResolutionResult for all store type patterns
- JobCertificateParser: extracts certificate/key/chain from
  ManagementJobConfiguration with explicit format detection
- PasswordResolver: resolves passwords from inline values or K8S secret
  references, centralising the "buddy password" pattern
- CertificateChainExtractor: parses PEM chains into leaf + intermediates,
  handling both bundled and pre-separated chain formats
- KeystoreOperations: JKS/PKCS12 read/write operations moved out of
  handlers into a standalone service

None of these services require a Kubernetes client, making them fully
unit-testable without network access.

* refactor: introduce handler strategy pattern for secret operations

Replace inline switch/if chains in JobBase with a proper Strategy pattern:

- ISecretHandler: contract for Inventory, Management, Discovery, and
  Reenrollment operations on a specific secret/store type
- SecretHandlerBase: shared infrastructure (client, logging, result helpers)
- SecretHandlerFactory: creates the correct handler from SecretType enum
- Per-type handlers: TlsSecretHandler, OpaqueSecretHandler,
  JksSecretHandler, Pkcs12SecretHandler, ClusterSecretHandler,
  NamespaceSecretHandler, CertificateSecretHandler (read-only)

Supporting additions:
- SecretTypes enum: typed representation of Kubernetes secret types with
  normalisation and IsTlsType/IsOpaqueType helpers
- K8SJobCertificate model: replaces ad-hoc certificate data passing
- Exceptions: StoreNotFoundException, InvalidK8SSecretException,
  JkSisPkcs12Exception — typed errors replace bare Exception throws
- ICertificateStoreSerializer + JKS/PKCS12 serializer implementations
  moved from StoreTypes/ to Serializers/ (interface renamed for clarity)

* refactor: split monolithic KubeClient into focused client components

KubeClient.cs was a 3000+ line file mixing authentication, kubeconfig
parsing, secret CRUD, and CSR operations. Split into:

- KubeconfigParser: parses kubeconfig JSON into typed configuration,
  validates required fields, provides clear error messages
- SecretOperations: Kubernetes secret CRUD (create, read, update, delete,
  list) with retry logic and structured logging
- CertificateOperations: CSR-specific operations (list, read, approve,
  inject certificate status)
- KubeClient (KubeCertificateManagerClient): now a thin coordinator
  that initialises the authenticated client and delegates to the above

Also removes unreachable code branches, converts string interpolation
log calls to structured logging throughout, and adds retry logic with
configurable backoff.

* refactor: restructure job classes by store type, remove X509Certificate2

Job structure (flat → per-store-type):
- Remove Jobs/Inventory.cs, Management.cs, Discovery.cs, Reenrollment.cs
  (monolithic files with large switch statements on store type)
- Add Jobs/Base/: K8SJobBase, InventoryBase, ManagementBase, DiscoveryBase,
  ReenrollmentBase — shared logic each job type delegates to its handler
- Add Jobs/StoreTypes/<Type>/: one class per operation per store type
  (7 store types × up to 4 operations = 26 concrete job classes)
- manifest.json updated to route each capability to its dedicated class

X509Certificate2 removal:
- Replace X509Certificate2 usage throughout with BouncyCastle types
- K8SCertificateContext replaces X509Certificate2-based SerializedStoreInfo
- LoggingUtilities updated: GetCertificateSummary now accepts BouncyCastle
  X509Certificate; RedactPassword no longer leaks password length

Version logging:
- JobBase reads AssemblyInformationalVersionAttribute at startup and logs
  "K8S Orchestrator Extension version: {Version}" on every job execution
  (baked in at build time by GitHub Actions via -p:Version=<tag>)

Also removes TestConsole (superseded by integration test suite) and
store_types.json (superseded by integration-manifest.json).

* feat: add CachedCertificateProvider and comprehensive test suite

Test infrastructure:
- CachedCertificateProvider: thread-safe cache for generated certificates;
  eliminates redundant RSA key generation across test collections (RSA 8192
  takes 30+ seconds per key — this alone cut full-suite runtime by ~60%)
- IntegrationTestFixture: shared kubeconfig loading, K8S client creation,
  namespace setup/teardown for all integration test collections
- SkipUnless attribute: skips integration tests when RUN_INTEGRATION_TESTS
  is not set, keeping unit test runs fast

New unit tests (zero network access):
- Services: StoreConfigurationParser, StorePathResolver, PasswordResolver,
  CertificateChainExtractor, JobCertificateParser, KeystoreOperations
- Handlers: SecretHandlerBase, SecretHandlerFactory, all handler types
  (no-network paths), alias routing regression
- Clients: KubeconfigParser, SecretOperations, CertificateOperations,
  KubeCertificateManagerClient
- Jobs: ManagementBase, DiscoveryBase, PAMUtilities, exception paths,
  K8SJobCertificate, K8SCertificateContext
- Utilities: LoggingUtilities (60 cases including DoesNotRevealLength),
  CertificateUtilities, LoggingSafetyTests
- Enums: SecretTypes

Updated integration tests: migrated all 7 store-type integration test
files to use IntegrationTestFixture and new job class namespaces.

Also adds scripts/analyze-coverage.py for coverage gap analysis.

* docs: update CHANGELOG, ARCHITECTURE.md, Development.md, README for v2.0.0

- CHANGELOG.md: document v2.0.0 breaking changes — new store type routing
  via per-store-type job classes, removed X509Certificate2 dependency,
  updated job configuration model
- docs/ARCHITECTURE.md: new file documenting the service/handler/job
  architecture, authentication flow, and extension points
- Development.md: updated testing guide with CachedCertificateProvider
  guidance, integration test setup, coverage targets
- README.md: regenerated from docsource/ with updated store type dialogs
- docsource/: updated content and added SVG store type dialog images for
  all 7 store types
- .github/workflows: add test-doctool workflow, update starter workflow
- scripts/store_types/: updated kfutil helper scripts
- terraform/: add Terraform module examples for all store types

* docs(architecture): remove incorrect reenrollment references

Reenrollment is not a supported operation. Remove it from the overview
sentence, fix the store type operations table (K8SJKS and K8SPKCS12 were
incorrectly listed as 'All + Reenrollment'), and remove ReenrollmentBase.cs
from the base class directory listing.

* docs: auto-generate README and documentation [skip ci]

* docs: update compatibility to include Command 24.x and 25.x

Update the compatibility statement and UO version matrix to explicitly
call out support for Keyfactor Command platform versions 24.x and 25.x,
and add a net10.0 row for Command 25.x and newer.

* docs: auto-generate README and documentation [skip ci]

* docs: call out .NET 8 and .NET 10 compatibility in README

Add explicit mention of net8.0/net10.0 dual-targeting to the
Compatibility section so users know which build to download without
having to dig into the installation table.

* docs: auto-generate README and documentation [skip ci]

* docs(changelog): add v2.0.0 entry

* docs(changelog): merge pre-rebase content into v2.0.0 and 1.3.0 entries

Add missing breaking changes (JobBase dead property removal, KeystoreManager
removal), terraform feature, and richer 1.3.0 bug fixes (create-if-missing,
buddy-secret password, alias routing) and refactor/test chores from the
break/major_refactor branch changelog.

* fix: add missing Serializers directory to fix build

The Serializers/ directory containing JKS and PKCS12 store serializers
was never committed, causing build failures when handler files attempted
to reference the Keyfactor.Extensions.Orchestrator.K8S.Serializers namespace.

* docs(auth): add client certificate auth as alternative to SA token

- Fix fragile grep/awk token lookup in get_service_account_creds.sh and
  create_service_account.sh — now uses direct jsonpath lookup with a
  clear error message if the token Secret is missing (k8s v1.22+)
- Add generate_client_cert_creds.sh: end-to-end script that applies RBAC,
  generates an RSA key, submits and approves a k8s CSR, and builds a
  client-cert kubeconfig in one step
- Add kubernetes_svc_account_cert_auth.yaml: ClusterRole + ClusterRoleBinding
  for cert-based auth (kind: User subject, no ServiceAccount required)
- Add example_kubeconfig_cert.json showing client-certificate-data layout
- Rewrite scripts/kubernetes/README.md to present both auth options equally
  with comparison table, quickstart, config reference, and manual steps
- Update docsource/content.md Requirements section to document both methods

* docs: auto-generate README and documentation [skip ci]

* feat(auth): add in-cluster pod identity as third authentication option

Plugin changes:
- KubeClient.GetKubeClient(): detect KUBERNETES_SERVICE_HOST and call
  InClusterConfig() when no kubeconfig is provided, using the projected
  service account token mounted by kubelet (auto-rotated every hour)
- JobBase.InitializeProperties(): allow empty KubeSvcCreds when running
  in-cluster instead of throwing ConfigurationException

Scripts/docs:
- Add keyfactor-orchestrator-deployment.yaml: Deployment manifest that
  runs the UO as a pod using the keyfactor-orchestrator-sa ServiceAccount
- Update scripts/kubernetes/README.md: add Option 3 to comparison table
  and full setup section (apply SA YAML, deploy, leave Server Password blank)
- Update docsource/content.md: document all three auth options equally

* docs: auto-generate README and documentation [skip ci]

* docs(auth): clarify in-cluster requires "No value" for Server Password in Command UI

* docs: auto-generate README and documentation [skip ci]

* fix(security): SOX/SOC2 compliance remediations and UseSSL bug fix

Compliance remediations (all findings were pre-existing on branch):
- Redact certificate bytes in UpdateOpaqueSecret log traces (CRIT-1)
- Log CSR certificate length only, not content preview (CRIT-2)
- Add structured AUDIT log entries (store_access, secret_read/write/delete)
  to ManagementBase, InventoryBase, DiscoveryBase, SecretOperations (CRIT-3)
- ValidateK8SName throws ArgumentException instead of warning; 5+ segment
  paths return Success=false and fail the job (CRIT-4)
- Zero KubeSvcCreds and ServerPassword after KubeClient construction (HIGH-1)
- RedactKubeconfig validates JSON structure before applying label; non-JSON
  returns POSSIBLY_MALFORMED_CREDENTIAL (HIGH-2)
- Silent catch blocks in JKS/PKCS12 serializers now log exception type (HIGH-3)
- PAM resolution outcome promoted from LogTrace to LogInformation (HIGH-4)
- TLS skip override promoted from LogWarning to LogError with structured
  SECURITY_CONFIG_OVERRIDE field (HIGH-5)
- ReadBuddyPass: make passwordSecretName discard explicit with _ (HIGH-6)
- HandleRemove returns Warning (not Success) when store not found so job
  history distinguishes no-op from actual removal (HIGH-7)
- Remove KubeSvcCreds from storeProperties dict after client construction (MED-1)
- StorePathResolver rejects 5+ segment paths (MED-4)
- Handler NotFound catch blocks use HttpOperationException status code
  comparison instead of ex.Message string matching (MED-5)
- Discovery InitializeStore wrapped in try/catch matching Inventory/Management
  pattern (MED-6)

Bug fix:
- UseSSL value from job config (config.UseSSL) was never forwarded to
  KubeCertificateManagerClient — TLS verification was always defaulting to
  true regardless of the store's Use SSL checkbox. Now captured in each
  InitializeStore overload and passed through InitializeKubeClient.

* security: remove GetPasswordCorrelationId and update changelog

Removes SHA-256 password correlation ID (MED-2) — low-entropy passwords
are reversible via dictionary attack and RedactPassword is already present
at all call sites. Updates CHANGELOG.md with all v2.0.0 changes from this
session including client cert auth, in-cluster auth, UseSSL fix, audit
logging, and compliance remediations.

* docs: remove duplicate content sections from generated README

Regenerated with fixed doctooldotnet (Keyfactor/doctooldotnet#9).
Named content.md sections were being emitted twice due to title mutation
before the custom-sections filter ran.

NOTE: Actions will revert this until doctooldotnet PR #9 is merged.

* docs: auto-generate README and documentation [skip ci]

* chore(ci): revert to old doctool

* fix(k8scert): ignore storepath for csr mode and add regression coverage

* fix(inventory): sanitize URL cluster names in discovery location strings (#88)

---------

Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(management): Stop reporting Success on silent secret write failures (#92)

* feat: Major refactor for v2.0.0 (#85) (#90)

* feat: `x509certificate2` removal (#73) (#79)

* feat: `x509certificate2` removal (#71)

* Update generated docs

* chore(lint): Fix PR review lint.

* Update generated docs

* test: unit tests for SeparateChain/IncludeCertChain conflict resolution in JobBase

Adds StorePropertiesParsingTests covering the four flag combinations so that
the override logic (SeparateChain forced to false when IncludeCertChain=false)
is caught at the unit level, not only by integration tests.

* Update generated docs

---------




* refactor: extract service layer from monolithic JobBase

Break domain logic out of JobBase into focused, testable services:

- StoreConfigurationParser: parses CertificateStoreDetails.Properties JSON
  into a typed StoreConfiguration, eliminating dynamic dispatch
- StorePathResolver: resolves StorePath strings (namespace/secret-name)
  into structured PathResolutionResult for all store type patterns
- JobCertificateParser: extracts certificate/key/chain from
  ManagementJobConfiguration with explicit format detection
- PasswordResolver: resolves passwords from inline values or K8S secret
  references, centralising the "buddy password" pattern
- CertificateChainExtractor: parses PEM chains into leaf + intermediates,
  handling both bundled and pre-separated chain formats
- KeystoreOperations: JKS/PKCS12 read/write operations moved out of
  handlers into a standalone service

None of these services require a Kubernetes client, making them fully
unit-testable without network access.

* refactor: introduce handler strategy pattern for secret operations

Replace inline switch/if chains in JobBase with a proper Strategy pattern:

- ISecretHandler: contract for Inventory, Management, Discovery, and
  Reenrollment operations on a specific secret/store type
- SecretHandlerBase: shared infrastructure (client, logging, result helpers)
- SecretHandlerFactory: creates the correct handler from SecretType enum
- Per-type handlers: TlsSecretHandler, OpaqueSecretHandler,
  JksSecretHandler, Pkcs12SecretHandler, ClusterSecretHandler,
  NamespaceSecretHandler, CertificateSecretHandler (read-only)

Supporting additions:
- SecretTypes enum: typed representation of Kubernetes secret types with
  normalisation and IsTlsType/IsOpaqueType helpers
- K8SJobCertificate model: replaces ad-hoc certificate data passing
- Exceptions: StoreNotFoundException, InvalidK8SSecretException,
  JkSisPkcs12Exception — typed errors replace bare Exception throws
- ICertificateStoreSerializer + JKS/PKCS12 serializer implementations
  moved from StoreTypes/ to Serializers/ (interface renamed for clarity)

* refactor: split monolithic KubeClient into focused client components

KubeClient.cs was a 3000+ line file mixing authentication, kubeconfig
parsing, secret CRUD, and CSR operations. Split into:

- KubeconfigParser: parses kubeconfig JSON into typed configuration,
  validates required fields, provides clear error messages
- SecretOperations: Kubernetes secret CRUD (create, read, update, delete,
  list) with retry logic and structured logging
- CertificateOperations: CSR-specific operations (list, read, approve,
  inject certificate status)
- KubeClient (KubeCertificateManagerClient): now a thin coordinator
  that initialises the authenticated client and delegates to the above

Also removes unreachable code branches, converts string interpolation
log calls to structured logging throughout, and adds retry logic with
configurable backoff.

* refactor: restructure job classes by store type, remove X509Certificate2

Job structure (flat → per-store-type):
- Remove Jobs/Inventory.cs, Management.cs, Discovery.cs, Reenrollment.cs
  (monolithic files with large switch statements on store type)
- Add Jobs/Base/: K8SJobBase, InventoryBase, ManagementBase, DiscoveryBase,
  ReenrollmentBase — shared logic each job type delegates to its handler
- Add Jobs/StoreTypes/<Type>/: one class per operation per store type
  (7 store types × up to 4 operations = 26 concrete job classes)
- manifest.json updated to route each capability to its dedicated class

X509Certificate2 removal:
- Replace X509Certificate2 usage throughout with BouncyCastle types
- K8SCertificateContext replaces X509Certificate2-based SerializedStoreInfo
- LoggingUtilities updated: GetCertificateSummary now accepts BouncyCastle
  X509Certificate; RedactPassword no longer leaks password length

Version logging:
- JobBase reads AssemblyInformationalVersionAttribute at startup and logs
  "K8S Orchestrator Extension version: {Version}" on every job execution
  (baked in at build time by GitHub Actions via -p:Version=<tag>)

Also removes TestConsole (superseded by integration test suite) and
store_types.json (superseded by integration-manifest.json).

* feat: add CachedCertificateProvider and comprehensive test suite

Test infrastructure:
- CachedCertificateProvider: thread-safe cache for generated certificates;
  eliminates redundant RSA key generation across test collections (RSA 8192
  takes 30+ seconds per key — this alone cut full-suite runtime by ~60%)
- IntegrationTestFixture: shared kubeconfig loading, K8S client creation,
  namespace setup/teardown for all integration test collections
- SkipUnless attribute: skips integration tests when RUN_INTEGRATION_TESTS
  is not set, keeping unit test runs fast

New unit tests (zero network access):
- Services: StoreConfigurationParser, StorePathResolver, PasswordResolver,
  CertificateChainExtractor, JobCertificateParser, KeystoreOperations
- Handlers: SecretHandlerBase, SecretHandlerFactory, all handler types
  (no-network paths), alias routing regression
- Clients: KubeconfigParser, SecretOperations, CertificateOperations,
  KubeCertificateManagerClient
- Jobs: ManagementBase, DiscoveryBase, PAMUtilities, exception paths,
  K8SJobCertificate, K8SCertificateContext
- Utilities: LoggingUtilities (60 cases including DoesNotRevealLength),
  CertificateUtilities, LoggingSafetyTests
- Enums: SecretTypes

Updated integration tests: migrated all 7 store-type integration test
files to use IntegrationTestFixture and new job class namespaces.

Also adds scripts/analyze-coverage.py for coverage gap analysis.

* docs: update CHANGELOG, ARCHITECTURE.md, Development.md, README for v2.0.0

- CHANGELOG.md: document v2.0.0 breaking changes — new store type routing
  via per-store-type job classes, removed X509Certificate2 dependency,
  updated job configuration model
- docs/ARCHITECTURE.md: new file documenting the service/handler/job
  architecture, authentication flow, and extension points
- Development.md: updated testing guide with CachedCertificateProvider
  guidance, integration test setup, coverage targets
- README.md: regenerated from docsource/ with updated store type dialogs
- docsource/: updated content and added SVG store type dialog images for
  all 7 store types
- .github/workflows: add test-doctool workflow, update starter workflow
- scripts/store_types/: updated kfutil helper scripts
- terraform/: add Terraform module examples for all store types

* docs(architecture): remove incorrect reenrollment references

Reenrollment is not a supported operation. Remove it from the overview
sentence, fix the store type operations table (K8SJKS and K8SPKCS12 were
incorrectly listed as 'All + Reenrollment'), and remove ReenrollmentBase.cs
from the base class directory listing.

* docs: auto-generate README and documentation [skip ci]

* docs: update compatibility to include Command 24.x and 25.x

Update the compatibility statement and UO version matrix to explicitly
call out support for Keyfactor Command platform versions 24.x and 25.x,
and add a net10.0 row for Command 25.x and newer.

* docs: auto-generate README and documentation [skip ci]

* docs: call out .NET 8 and .NET 10 compatibility in README

Add explicit mention of net8.0/net10.0 dual-targeting to the
Compatibility section so users know which build to download without
having to dig into the installation table.

* docs: auto-generate README and documentation [skip ci]

* docs(changelog): add v2.0.0 entry

* docs(changelog): merge pre-rebase content into v2.0.0 and 1.3.0 entries

Add missing breaking changes (JobBase dead property removal, KeystoreManager
removal), terraform feature, and richer 1.3.0 bug fixes (create-if-missing,
buddy-secret password, alias routing) and refactor/test chores from the
break/major_refactor branch changelog.

* fix: add missing Serializers directory to fix build

The Serializers/ directory containing JKS and PKCS12 store serializers
was never committed, causing build failures when handler files attempted
to reference the Keyfactor.Extensions.Orchestrator.K8S.Serializers namespace.

* docs(auth): add client certificate auth as alternative to SA token

- Fix fragile grep/awk token lookup in get_service_account_creds.sh and
  create_service_account.sh — now uses direct jsonpath lookup with a
  clear error message if the token Secret is missing (k8s v1.22+)
- Add generate_client_cert_creds.sh: end-to-end script that applies RBAC,
  generates an RSA key, submits and approves a k8s CSR, and builds a
  client-cert kubeconfig in one step
- Add kubernetes_svc_account_cert_auth.yaml: ClusterRole + ClusterRoleBinding
  for cert-based auth (kind: User subject, no ServiceAccount required)
- Add example_kubeconfig_cert.json showing client-certificate-data layout
- Rewrite scripts/kubernetes/README.md to present both auth options equally
  with comparison table, quickstart, config reference, and manual steps
- Update docsource/content.md Requirements section to document both methods

* docs: auto-generate README and documentation [skip ci]

* feat(auth): add in-cluster pod identity as third authentication option

Plugin changes:
- KubeClient.GetKubeClient(): detect KUBERNETES_SERVICE_HOST and call
  InClusterConfig() when no kubeconfig is provided, using the projected
  service account token mounted by kubelet (auto-rotated every hour)
- JobBase.InitializeProperties(): allow empty KubeSvcCreds when running
  in-cluster instead of throwing ConfigurationException

Scripts/docs:
- Add keyfactor-orchestrator-deployment.yaml: Deployment manifest that
  runs the UO as a pod using the keyfactor-orchestrator-sa ServiceAccount
- Update scripts/kubernetes/README.md: add Option 3 to comparison table
  and full setup section (apply SA YAML, deploy, leave Server Password blank)
- Update docsource/content.md: document all three auth options equally

* docs: auto-generate README and documentation [skip ci]

* docs(auth): clarify in-cluster requires "No value" for Server Password in Command UI

* docs: auto-generate README and documentation [skip ci]

* fix(security): SOX/SOC2 compliance remediations and UseSSL bug fix

Compliance remediations (all findings were pre-existing on branch):
- Redact certificate bytes in UpdateOpaqueSecret log traces (CRIT-1)
- Log CSR certificate length only, not content preview (CRIT-2)
- Add structured AUDIT log entries (store_access, secret_read/write/delete)
  to ManagementBase, InventoryBase, DiscoveryBase, SecretOperations (CRIT-3)
- ValidateK8SName throws ArgumentException instead of warning; 5+ segment
  paths return Success=false and fail the job (CRIT-4)
- Zero KubeSvcCreds and ServerPassword after KubeClient construction (HIGH-1)
- RedactKubeconfig validates JSON structure before applying label; non-JSON
  returns POSSIBLY_MALFORMED_CREDENTIAL (HIGH-2)
- Silent catch blocks in JKS/PKCS12 serializers now log exception type (HIGH-3)
- PAM resolution outcome promoted from LogTrace to LogInformation (HIGH-4)
- TLS skip override promoted from LogWarning to LogError with structured
  SECURITY_CONFIG_OVERRIDE field (HIGH-5)
- ReadBuddyPass: make passwordSecretName discard explicit with _ (HIGH-6)
- HandleRemove returns Warning (not Success) when store not found so job
  history distinguishes no-op from actual removal (HIGH-7)
- Remove KubeSvcCreds from storeProperties dict after client construction (MED-1)
- StorePathResolver rejects 5+ segment paths (MED-4)
- Handler NotFound catch blocks use HttpOperationException status code
  comparison instead of ex.Message string matching (MED-5)
- Discovery InitializeStore wrapped in try/catch matching Inventory/Management
  pattern (MED-6)

Bug fix:
- UseSSL value from job config (config.UseSSL) was never forwarded to
  KubeCertificateManagerClient — TLS verification was always defaulting to
  true regardless of the store's Use SSL checkbox. Now captured in each
  InitializeStore overload and passed through InitializeKubeClient.

* security: remove GetPasswordCorrelationId and update changelog

Removes SHA-256 password correlation ID (MED-2) — low-entropy passwords
are reversible via dictionary attack and RedactPassword is already present
at all call sites. Updates CHANGELOG.md with all v2.0.0 changes from this
session including client cert auth, in-cluster auth, UseSSL fix, audit
logging, and compliance remediations.

* docs: remove duplicate content sections from generated README

Regenerated with fixed doctooldotnet (Keyfactor/doctooldotnet#9).
Named content.md sections were being emitted twice due to title mutation
before the custom-sections filter ran.

NOTE: Actions will revert this until doctooldotnet PR #9 is merged.

* docs: auto-generate README and documentation [skip ci]

* chore(ci): revert to old doctool

* fix(k8scert): ignore storepath for csr mode and add regression coverage

* fix(inventory): sanitize URL cluster names in discovery location strings (#88)

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

* fix(management): stop reporting Success on silent secret write failures

ManagementBase.HandleAdd discarded the handler's returned V1Secret and
unconditionally returned Success, so a K8S write that failed without
throwing (e.g. a pre-existing secret update that silently no-opped) was
still reported to Command as a successful deployment. HandleAdd now
checks the returned V1Secret and fails the job with an actionable
message when it is null.

KubeCertificateManagerClient.CreateOrUpdateCertificateStoreSecret used
a blind create-then-catch flow, routing to the update path only on a
free-text e.Message.Contains("Conflict") match; any other
HttpOperationException (e.g. 403 from RBAC scoping) fell through and
returned null silently. It now reads the secret first (existence check
via SecretOperations.GetSecret, which only swallows a typed 404) and
branches to update when it already exists, matching the JKS/PKCS12
path. A typed HttpStatusCode.Conflict on the create call still falls
back to update for a genuine create race; every other
HttpOperationException now propagates instead of being swallowed.

Adds regression coverage for both fixes: ManagementBaseTests covers the
null-handler-result -> Failure path, and the new
KubeClientCreateOrUpdateSecretTests covers create/update routing plus
403 propagation on both the existence check and the create call.

Fixes #91

* fix(make): pin test-cluster-cleanup to the integration test kube context

test-cluster-cleanup used the current kubectl context, so with a different
active context (e.g. docker-desktop) it silently cleaned the wrong cluster
and stale test namespaces accumulated on kf-integrations for months —
making cluster-wide inventory integration tests pathologically slow.

Introduce TEST_KUBE_CONTEXT (default kf-integrations, overridable) and pass
--context to all kubectl calls in the cleanup target.

* test(k8scert): skip cluster-wide CSR inventory test on unpopulated clusters

Inventory_ClusterWideMode_InventoriesAllIssuedCsrs_InCurrentCluster hard-
asserted >=30 issued CSRs, which only holds on the populated lab cluster —
it can never pass on the ephemeral cluster CI provisions, and this PR's CI
run was the first to execute it there. Soft-skip when the cluster has no
issued CSRs; behavior against the lab cluster is unchanged (verified via
make test-store-cert, 10 passed on both TFMs).

* chore(deps): bump GitHub Actions versions

Incorporates the dependabot version bumps from PRs #80-84:
- actions/checkout v4 -> v6 (#80)
- actions/setup-dotnet v4 -> v5 (#81)
- softprops/action-gh-release v1 -> v2 (#82)
- actions/github-script v7 -> v8 (#83)
- actions/upload-artifact v4 -> v7 (#84)

* style(tests): use ternary in SetupRead per code-quality bot feedback on PR #92

Both branches assign to the same seq variable — a ternary expresses that
more directly than if/else.

---------

Co-authored-by: Morgan Gangwere <470584+indrora@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>

---------

Co-authored-by: spb <1661003+spbsoluble@users.noreply.github.com>
Co-authored-by: Keyfactor <keyfactor@keyfactor.github.io>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants