From f10c2982f3892fe0c212f9ad132038c9884f95f8 Mon Sep 17 00:00:00 2001 From: Brian Hill <76450501+bhillkeyfactor@users.noreply.github.com> Date: Tue, 7 Jul 2026 13:21:07 -0400 Subject: [PATCH 01/10] Update CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f89b88f..3346fbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -# v1.4.0 +# v2.0.0 * Dns Plugin Support * Cname Proxy Support From 936bcdc3bed4af7c9ac4bb1617dee5643cfd6751 Mon Sep 17 00:00:00 2001 From: Brian Hill Date: Tue, 7 Jul 2026 13:58:47 -0400 Subject: [PATCH 02/10] Fix validator resolution regression for non-delegated challenges The framework's ResolveDomainValidator matches configured provider domain patterns (e.g. *.zone.com) against the certificate/zone name, not the _acme-challenge record name. Resolving on the raw resolved record name broke the non-delegated path because the _acme-challenge prefix fails the one-level wildcard match. Resolve the validator on the cert domain when no CNAME delegation exists, and on the resolved terminal target when it does, so both the direct and cross-zone delegated cases select the correct provider. --- AcmeCaPlugin/AcmeCaPlugin.cs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/AcmeCaPlugin/AcmeCaPlugin.cs b/AcmeCaPlugin/AcmeCaPlugin.cs index bd842fe..fd7a2d8 100644 --- a/AcmeCaPlugin/AcmeCaPlugin.cs +++ b/AcmeCaPlugin/AcmeCaPlugin.cs @@ -685,21 +685,38 @@ private async Task ProcessAuthorizations(AcmeClient acmeClient, OrderDetails ord async () => await cnameResolver.ResolveChallengeTargetAsync(validation.DnsRecordName), detail: $"from {validation.DnsRecordName}"); + // Decide which name to resolve the DNS provider plugin against. The framework + // matches validators against the (wildcard) domain patterns configured per + // provider, keyed on the certificate/zone name (e.g. *.keyfactor.ssl4saas.com) + // -- NOT the _acme-challenge record name. So: + // * No delegation -> resolve on the cert domain (www.example.com), exactly + // as before delegation support existed. + // * CNAME delegated -> resolve on the terminal target (host.validation-zone), + // so the provider that owns the delegation zone is chosen. + // Note the resolver returns the original name unchanged when no CNAME exists, + // and that original name still carries the _acme-challenge prefix, which would + // not match the configured wildcard -- hence the explicit branch here. + bool isDelegated = !string.Equals( + recordName?.TrimEnd('.'), + validation.DnsRecordName?.TrimEnd('.'), + StringComparison.OrdinalIgnoreCase); + var validatorLookupName = isDelegated ? recordName : domain; + var domainValidator = flow.Step($"ResolveValidator:{domain}", () => { - var v = _validatorFactory.ResolveDomainValidator(recordName, DNS_CHALLENGE_TYPE); + var v = _validatorFactory.ResolveDomainValidator(validatorLookupName, DNS_CHALLENGE_TYPE); if (v == null) { throw new InvalidOperationException( - $"Failed to resolve domain validator for '{recordName}' (challenge for '{domain}'). " + + $"Failed to resolve domain validator for '{validatorLookupName}' (challenge for '{domain}'). " + "Ensure the appropriate DNS provider plugin is deployed and configured for the zone that hosts the (possibly CNAME-delegated) challenge record."); } return v; }); - _logger.LogInformation("Using domain validator: {ValidatorType} for record: {RecordName} (domain: {Domain})", - domainValidator.GetType().Name, recordName, domain); + _logger.LogInformation("Using domain validator: {ValidatorType} resolved on {LookupName} (record: {RecordName}, domain: {Domain}, delegated: {Delegated})", + domainValidator.GetType().Name, validatorLookupName, recordName, domain, isDelegated); await flow.StepAsync($"StageValidation:{domain}", async () => From c272920a58c08082efa53280ae7da44128435c6a Mon Sep 17 00:00:00 2001 From: Brian Hill Date: Wed, 8 Jul 2026 10:19:13 -0400 Subject: [PATCH 03/10] docs: document CNAME delegation (proxy) lookup Add a CNAME Delegation section to docsource/configuration.md describing why challenge names are delegated to an isolated validation zone, how the CnameResolver follows a multi-level CNAME chain to its terminus, how the DNS provider plugin is selected against the resolved target (enabling cross-provider delegation), the loop/depth safety guards, and private-zone resolution via DnsVerificationServer. Update the enrollment flow summary to include the CNAME resolution step. --- docsource/configuration.md | 59 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/docsource/configuration.md b/docsource/configuration.md index 186f285..1ec2e32 100644 --- a/docsource/configuration.md +++ b/docsource/configuration.md @@ -39,9 +39,10 @@ Additional DNS providers can be added by extending the included `IDnsProvider` i 2. Plugin initializes ACME client and creates a new order. 3. For each domain: a. Retrieve DNS-01 challenge. - b. Use the configured DNS provider to publish challenge record. - c. Wait for DNS propagation and validate record. - d. Notify ACME provider to trigger validation. + b. Resolve any CNAME delegation: follow the CNAME chain from `_acme-challenge.` to its terminal target (see CNAME Delegation below). + c. Select the DNS provider plugin for the zone that owns the (resolved) record name and publish the challenge TXT record there. + d. Wait for DNS propagation and validate record. + e. Notify ACME provider to trigger validation. 4. Once all challenges are valid, finalize the order using CSR. 5. Download the signed certificate from ACME provider. 6. Return PEM certificate to the Gateway. @@ -86,6 +87,58 @@ This logic is handled by the `DnsVerificationHelper` class and ensures a high-co +
+🔗 CNAME Delegation (Proxy) Lookup + +Many organizations do not want ACME automation to hold write access to their production DNS zone. The industry-standard pattern is to **delegate just the ACME challenge name** to a separate, isolated validation zone using a `CNAME` record. The plugin supports this transparently. + +#### Why delegate? + +A `CNAME` at `_acme-challenge.` points challenge validation at another zone. ACME automation then only needs write access to that isolated zone — never the production zone. A `CNAME` also cannot coexist with any other record type at the same name (RFC 1034), so the TXT record **must** be created at the CNAME's target, not at the original challenge name. + +#### How the plugin resolves it + +Before publishing the challenge record, the plugin runs the `CnameResolver`, which: + +1. Issues a DNS `CNAME` query for `_acme-challenge.`. +2. **Follows the chain to its terminus.** Delegation can be nested multiple levels deep (`A → B → C → â€Ļ`); the resolver re-queries at each hop and stops only when it reaches a name that has no further `CNAME`. That terminal name is where the TXT record is created. +3. Returns the original name unchanged when **no** `CNAME` exists — so non-delegated domains behave exactly as before (fully backwards compatible). + +Safety guards: the resolver detects loops (a name that reappears in the chain) and enforces a maximum depth of **10 hops**, logging a warning and stopping at the last good name rather than looping forever. + +#### Provider selection follows the delegation + +The DNS provider plugin is resolved against the **name where the record actually lands**: + +- **No delegation** → the provider is selected for the certificate domain (e.g. `www.example.com`). +- **Delegated** → the provider is selected for the **resolved terminal target** (e.g. `abc123.acme-validation.net`). + +This means a challenge delegated into a zone hosted by a *different* DNS provider is routed to the plugin that owns that zone. Propagation checks and cleanup also operate on the resolved name. + +> â„šī¸ Provider selection uses the AnyCA Gateway's Domain Validation configuration, which matches a configured (optionally wildcard) domain pattern one label at a time. Ensure the **delegation target's zone** is covered by a Domain Validation entry — e.g. a target of `abc123.acme-validation.net` needs a validator whose domain matches `*.acme-validation.net`. + +#### Example (multi-level delegation across providers) + +```text +Cert domain: www.example.com (production zone, e.g. GoDaddy) +Challenge name: _acme-challenge.www.example.com + +DNS records (static, created once): + _acme-challenge.www.example.com CNAME hop1.example.com (GoDaddy) + hop1.example.com CNAME hop2.example.com (GoDaddy) + hop2.example.com CNAME val.acme-zone.net (points into the validation zone) + +Resolution + placement: + _acme-challenge.www.example.com → hop1 → hop2 → val.acme-zone.net (terminal) + Provider plugin selected for: acme-zone.net (the zone that owns val.acme-zone.net) + TXT record created at: val.acme-zone.net + ACME CA queries _acme-challenge.www.example.com, follows the CNAMEs, finds the TXT ✅ +``` + +> â„šī¸ **Private/internal delegation zones:** set `DnsVerificationServer` to your authoritative DNS server IP. The `CnameResolver` honors it for the CNAME lookups; otherwise public resolvers (Google, Cloudflare, Quad9) are used. + +
+
🔑 Credential Flow From 5d5dc40109313ad4e00a0a41a9f0912a33a9eaf3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Jul 2026 14:19:47 +0000 Subject: [PATCH 04/10] docs: auto-generate README and documentation [skip ci] --- README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 56 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index e6523cb..c940d9b 100644 --- a/README.md +++ b/README.md @@ -73,9 +73,10 @@ Additional DNS providers can be added by extending the included `IDnsProvider` i 2. Plugin initializes ACME client and creates a new order. 3. For each domain: a. Retrieve DNS-01 challenge. - b. Use the configured DNS provider to publish challenge record. - c. Wait for DNS propagation and validate record. - d. Notify ACME provider to trigger validation. + b. Resolve any CNAME delegation: follow the CNAME chain from `_acme-challenge.` to its terminal target (see CNAME Delegation below). + c. Select the DNS provider plugin for the zone that owns the (resolved) record name and publish the challenge TXT record there. + d. Wait for DNS propagation and validate record. + e. Notify ACME provider to trigger validation. 4. Once all challenges are valid, finalize the order using CSR. 5. Download the signed certificate from ACME provider. 6. Return PEM certificate to the Gateway. @@ -124,6 +125,58 @@ This logic is handled by the `DnsVerificationHelper` class and ensures a high-co
+
+🔗 CNAME Delegation (Proxy) Lookup + +Many organizations do not want ACME automation to hold write access to their production DNS zone. The industry-standard pattern is to **delegate just the ACME challenge name** to a separate, isolated validation zone using a `CNAME` record. The plugin supports this transparently. + +#### Why delegate? + +A `CNAME` at `_acme-challenge.` points challenge validation at another zone. ACME automation then only needs write access to that isolated zone — never the production zone. A `CNAME` also cannot coexist with any other record type at the same name (RFC 1034), so the TXT record **must** be created at the CNAME's target, not at the original challenge name. + +#### How the plugin resolves it + +Before publishing the challenge record, the plugin runs the `CnameResolver`, which: + +1. Issues a DNS `CNAME` query for `_acme-challenge.`. +2. **Follows the chain to its terminus.** Delegation can be nested multiple levels deep (`A → B → C → â€Ļ`); the resolver re-queries at each hop and stops only when it reaches a name that has no further `CNAME`. That terminal name is where the TXT record is created. +3. Returns the original name unchanged when **no** `CNAME` exists — so non-delegated domains behave exactly as before (fully backwards compatible). + +Safety guards: the resolver detects loops (a name that reappears in the chain) and enforces a maximum depth of **10 hops**, logging a warning and stopping at the last good name rather than looping forever. + +#### Provider selection follows the delegation + +The DNS provider plugin is resolved against the **name where the record actually lands**: + +- **No delegation** → the provider is selected for the certificate domain (e.g. `www.example.com`). +- **Delegated** → the provider is selected for the **resolved terminal target** (e.g. `abc123.acme-validation.net`). + +This means a challenge delegated into a zone hosted by a *different* DNS provider is routed to the plugin that owns that zone. Propagation checks and cleanup also operate on the resolved name. + +> â„šī¸ Provider selection uses the AnyCA Gateway's Domain Validation configuration, which matches a configured (optionally wildcard) domain pattern one label at a time. Ensure the **delegation target's zone** is covered by a Domain Validation entry — e.g. a target of `abc123.acme-validation.net` needs a validator whose domain matches `*.acme-validation.net`. + +#### Example (multi-level delegation across providers) + +```text +Cert domain: www.example.com (production zone, e.g. GoDaddy) +Challenge name: _acme-challenge.www.example.com + +DNS records (static, created once): + _acme-challenge.www.example.com CNAME hop1.example.com (GoDaddy) + hop1.example.com CNAME hop2.example.com (GoDaddy) + hop2.example.com CNAME val.acme-zone.net (points into the validation zone) + +Resolution + placement: + _acme-challenge.www.example.com → hop1 → hop2 → val.acme-zone.net (terminal) + Provider plugin selected for: acme-zone.net (the zone that owns val.acme-zone.net) + TXT record created at: val.acme-zone.net + ACME CA queries _acme-challenge.www.example.com, follows the CNAMEs, finds the TXT ✅ +``` + +> â„šī¸ **Private/internal delegation zones:** set `DnsVerificationServer` to your authoritative DNS server IP. The `CnameResolver` honors it for the CNAME lookups; otherwise public resolvers (Google, Cloudflare, Quad9) are used. + +
+
🔑 Credential Flow From 87f017a25a2820ffd357d27638bec569e2fb8234 Mon Sep 17 00:00:00 2001 From: Brian Hill Date: Wed, 8 Jul 2026 10:29:48 -0400 Subject: [PATCH 05/10] docs: replace bundled DNS provider lists with pluggable model DNS providers are now standalone, pluggable plugins deployed alongside the AnyCA Gateway rather than built into this ACME plugin. Remove the hardcoded "supported DNS providers" lists, per-provider credential/config tables, RFC 2136 setup, and the obsolete IDnsProvider/DnsProviderFactory "adding new providers" guidance. Point instead to the Keyfactor -dnsplugin repositories query as the authoritative source, and document that providers are configured via the Gateway's Domain Validation config and resolved per domain (including CNAME-delegated targets). --- docsource/configuration.md | 165 +++++++------------------------------ 1 file changed, 28 insertions(+), 137 deletions(-) diff --git a/docsource/configuration.md b/docsource/configuration.md index 1ec2e32..0ad1add 100644 --- a/docsource/configuration.md +++ b/docsource/configuration.md @@ -18,17 +18,14 @@ This plugin has been tested and confirmed to work with the following ACME provid It is designed to be provider-agnostic and should work with any standards-compliant ACME server. -### 🌐 Supported DNS Providers -DNS-01 challenge automation is supported through the following providers: -- **Google Cloud DNS** -- **AWS Route 53** -- **Azure DNS** -- **Cloudflare** -- **NS1** -- **Infoblox** -- **RFC 2136 Dynamic DNS** (BIND with TSIG authentication) - -Additional DNS providers can be added by extending the included `IDnsProvider` interface. +### 🌐 DNS Providers (Pluggable) +DNS-01 challenge automation is handled by **separate, pluggable DNS provider plugins** that are deployed alongside the AnyCA Gateway — they are no longer built into this plugin. The Gateway resolves the appropriate DNS provider plugin per domain at enrollment time. + +For the current list of available DNS provider plugins, see the Keyfactor GitHub organization: + +👉 **[Keyfactor DNS provider plugins (`-dnsplugin`)](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin)** + +Each plugin repository documents its own supported authentication methods and configuration keys. New DNS providers can be added by publishing a new plugin that implements the Gateway's `IDomainValidator` interface — no change to this ACME plugin is required. --- @@ -64,17 +61,15 @@ The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gatewa This plugin automates DNS-01 challenges using pluggable DNS provider implementations. These providers create and remove TXT records to prove domain control to ACME servers.
-✅ Supported DNS Providers - -| Provider | Auth Methods Supported | Config Keys Required | -|--------------|-----------------------------------------------|--------------------------------------------------------| -| Google DNS | Service Account Key (file or JSON), or ADC | `Google_ServiceAccountKeyPath`, `Google_ServiceAccountKeyJson`, `Google_ProjectId` | -| AWS Route 53 | Access Key/Secret or IAM Role | `AwsRoute53_AccessKey`, `AwsRoute53_SecretKey` | -| Azure DNS | Client Secret or Managed Identity | `Azure_TenantId`, `Azure_ClientId`, `Azure_ClientSecret`, `Azure_SubscriptionId` | -| Cloudflare | API Token | `Cloudflare_ApiToken` | -| NS1 | API Key | `Ns1_ApiKey` | -| Infoblox | Username/Password (Basic Auth) | `Infoblox_Host`, `Infoblox_Username`, `Infoblox_Password` | -| RFC 2136 | TSIG Key (BIND) | `Rfc2136_Server`, `Rfc2136_Zone`, `Rfc2136_TsigKeyName`, `Rfc2136_TsigKey` | +🔌 Available DNS Provider Plugins + +DNS providers are distributed as **standalone plugins**, each in its own repository, and are deployed alongside the AnyCA Gateway rather than bundled into this ACME plugin. This lets you add or upgrade a DNS provider without rebuilding the ACME plugin. + +For the current, authoritative list of available DNS provider plugins, query the Keyfactor GitHub organization: + +👉 **[github.com/orgs/Keyfactor/repositories?q=-dnsplugin](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin)** + +Each plugin's own repository is the source of truth for its supported authentication methods, required configuration keys, and setup instructions. Configure the DNS provider(s) through the AnyCA Gateway's **Domain Validation** configuration; the Gateway resolves the correct plugin per domain at enrollment time.
@@ -140,134 +135,34 @@ Resolution + placement:
-🔑 Credential Flow - -Each provider supports multiple credential strategies: - -- **Google DNS**: - - ✅ **Service Account Key File** (via `Google_ServiceAccountKeyPath`) - - ✅ **Service Account Key JSON** (via `Google_ServiceAccountKeyJson` - paste JSON directly) - - ✅ **Application Default Credentials** (e.g., GCP Workload Identity or developer auth) +🔑 Provider Credentials & Configuration -- **AWS Route 53**: - - ✅ **Access/Secret Keys** (`AwsRoute53_AccessKey`, `AwsRoute53_SecretKey`) - - ✅ **IAM Role via EC2 Instance Metadata** (no explicit credentials) - -- **Azure DNS**: - - ✅ **Client Secret** (explicit `TenantId`, `ClientId`, `ClientSecret`) - - ✅ **Managed Identity** or environment-based credentials via `DefaultAzureCredential` - -- **Cloudflare**: - - ✅ **Bearer API Token** for zone-level DNS control - -- **NS1**: - - ✅ **API Key** passed in header `X-NSONE-Key` - -- **Infoblox**: - - ✅ **Username/Password** (Basic Auth via WAPI REST API) - - Optional: `Infoblox_WapiVersion` (defaults to `2.12`) - - Optional: `Infoblox_IgnoreSslErrors` for self-signed certificates - -- **RFC 2136 (BIND)**: - - ✅ **TSIG Key** for secure dynamic DNS updates - - Supports algorithms: `hmac-md5`, `hmac-sha1`, `hmac-sha256`, `hmac-sha384`, `hmac-sha512` - - Default algorithm: `hmac-sha256` (recommended) - - Optional: `Rfc2136_Port` (defaults to `53`) +Authentication methods and required configuration keys are specific to each DNS provider and are **documented in that provider's own plugin repository** — see the [`-dnsplugin` repositories](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin). Credentials and settings are supplied through the AnyCA Gateway's **Domain Validation** configuration for the chosen plugin, not in this ACME plugin's configuration.
-đŸĸ On-Premise DNS (RFC 2136) - -The RFC 2136 provider enables ACME DNS-01 challenges with on-premise DNS servers that support dynamic updates, including: - -- **BIND** (Berkeley Internet Name Domain) -- **PowerDNS** (with dynamic update support) -- Any DNS server supporting RFC 2136 with TSIG authentication - -#### Configuration Requirements +đŸĸ On-Premise / Private DNS -| Field | Description | Required | -|-------|-------------|----------| -| `Rfc2136_Server` | DNS server hostname or IP address | ✅ Yes | -| `Rfc2136_Zone` | DNS zone to update (e.g., `example.com`) | ✅ Yes | -| `Rfc2136_TsigKeyName` | TSIG key name (e.g., `acme-update-key`) | ✅ Yes | -| `Rfc2136_TsigKey` | Base64-encoded TSIG secret key | ✅ Yes | -| `Rfc2136_TsigAlgorithm` | TSIG algorithm (default: `hmac-sha256`) | Optional | -| `Rfc2136_Port` | DNS server port (default: `53`) | Optional | -| `DnsVerificationServer` | DNS server IP for verification (for private zones) | Optional | +On-premise and private DNS support (e.g. RFC 2136 dynamic updates against BIND/PowerDNS with TSIG) is provided by the corresponding DNS provider plugin — see its repository under the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) for TSIG key generation, server/zone settings, and setup examples. -#### Generating TSIG Keys - -**For BIND:** -bash - -**Generate a TSIG key using tsig-keygen (BIND 9.10+)** -tsig-keygen -a hmac-sha256 acme-update-key - -**Output example:** -key "acme-update-key" { - algorithm hmac-sha256; - secret "base64encodedkey=="; -}; - - -#### BIND Configuration Example - -Add to `named.conf`: - -key "acme-update-key" { - algorithm hmac-sha256; - secret "YourBase64EncodedKeyHere=="; -}; - -zone "example.com" { - type master; - file "/var/named/example.com.zone"; - allow-update { key "acme-update-key"; }; -}; - - -> âš ī¸ **Security Note:** TSIG keys should be treated as secrets. Store them securely and use strong keys generated with cryptographically secure random number generators. - -> âš ī¸ **Private DNS Zones:** For private/local DNS zones (e.g., `.local`), set `DnsVerificationServer` to your authoritative DNS server IP so the plugin can verify TXT record propagation. +> âš ī¸ **Private DNS Zones:** For private/local DNS zones (e.g., `.local`) that are not reachable via public resolvers, set `DnsVerificationServer` to your authoritative DNS server IP. This ACME plugin uses it both to verify TXT record propagation and to resolve CNAME delegation chains.
🧩 Adding New DNS Providers -To add support for new DNS services: - -1. Implement the `IDnsProvider` interface: - ```csharp - public interface IDnsProvider - { - Task CreateRecordAsync(string recordName, string txtValue); - Task DeleteRecordAsync(string recordName); - } - ``` - -2. Register the new provider in the `DnsProviderFactory`: - ```csharp - case "yourprovider": - return new YourCustomDnsProvider(config.YourProviderConfigValues...); - ``` +DNS providers are independent plugins, so adding a new one requires **no change to this ACME plugin**. Publish a plugin that implements the AnyCA Gateway's `IDomainValidator` interface (create/cleanup the validation record for a domain), deploy it alongside the Gateway, and configure it under **Domain Validation**. The Gateway will resolve it per domain at enrollment time. -3. Use zone detection logic similar to `GoogleDnsProvider`, `AzureDnsProvider`, or `Ns1DnsProvider`. - -Each provider is instantiated dynamically based on the `DnsProvider` field in the `AcmeClientConfig`. - -> 🔁 This modular DNS system ensures challenge automation works across cloud providers and is easily extensible. +Use any existing plugin in the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) as a reference implementation.
-🔒 CA-Level DNS Provider Binding +🔒 Per-Domain DNS Provider Resolution -Each ACME/DNS combination is supported **at the CA level**, meaning that only **one DNS provider** is configured per CA entry in Keyfactor. This ensures a clear and isolated challenge path for each ACME CA connector instance. - -If you need to support multiple DNS zones/providers (e.g., both AWS and Cloudflare), configure **separate CA entries**, each with its own DNS provider configuration. +You can configure **multiple DNS provider plugins** and the Gateway selects the appropriate one for each domain based on your **Domain Validation** configuration (matching a configured, optionally wildcard, domain pattern). This also means a single certificate with SANs across different zones/providers can be validated using different plugins, and CNAME-delegated challenges are routed to the plugin that owns the delegation target's zone (see **CNAME Delegation** above).
@@ -539,13 +434,9 @@ If `AccountStoragePath` is not set and `%APPDATA%` is unavailable, the plugin de
-🌐 Google Cloud DNS in Containers - -For Google Cloud DNS in container environments, you have three authentication options: +🌐 DNS Provider Authentication in Containers -1. **Workload Identity (GKE)**: No explicit credentials needed; uses pod identity. -2. **JSON key in config**: Paste the service account JSON directly into `Google_ServiceAccountKeyJson`. -3. **Mounted JSON file**: Mount the service account key file and set `Google_ServiceAccountKeyPath`. +DNS provider credentials in containerized environments are handled by each **DNS provider plugin**, not by this ACME plugin. Options such as cloud-native identity (GKE Workload Identity, EKS IRSA, AKS Pod Identity), mounted key files, or config-supplied secrets depend on the provider — see the relevant plugin under the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) for its supported container authentication methods.
From a19cce6ef952b7bfb6e7cd67b0bc5e61eb65ed5d Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Jul 2026 14:30:39 +0000 Subject: [PATCH 06/10] docs: auto-generate README and documentation [skip ci] --- README.md | 164 ++++++++++-------------------------------------------- 1 file changed, 28 insertions(+), 136 deletions(-) diff --git a/README.md b/README.md index c940d9b..3cf7705 100644 --- a/README.md +++ b/README.md @@ -52,17 +52,14 @@ This plugin has been tested and confirmed to work with the following ACME provid It is designed to be provider-agnostic and should work with any standards-compliant ACME server. -### 🌐 Supported DNS Providers -DNS-01 challenge automation is supported through the following providers: -- **Google Cloud DNS** -- **AWS Route 53** -- **Azure DNS** -- **Cloudflare** -- **NS1** -- **Infoblox** -- **RFC 2136 Dynamic DNS** (BIND with TSIG authentication) - -Additional DNS providers can be added by extending the included `IDnsProvider` interface. +### 🌐 DNS Providers (Pluggable) +DNS-01 challenge automation is handled by **separate, pluggable DNS provider plugins** that are deployed alongside the AnyCA Gateway — they are no longer built into this plugin. The Gateway resolves the appropriate DNS provider plugin per domain at enrollment time. + +For the current list of available DNS provider plugins, see the Keyfactor GitHub organization: + +👉 **[Keyfactor DNS provider plugins (`-dnsplugin`)](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin)** + +Each plugin repository documents its own supported authentication methods and configuration keys. New DNS providers can be added by publishing a new plugin that implements the Gateway's `IDomainValidator` interface — no change to this ACME plugin is required. --- @@ -102,17 +99,15 @@ The Acme AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor custo This plugin automates DNS-01 challenges using pluggable DNS provider implementations. These providers create and remove TXT records to prove domain control to ACME servers.
-✅ Supported DNS Providers - -| Provider | Auth Methods Supported | Config Keys Required | -|--------------|-----------------------------------------------|--------------------------------------------------------| -| Google DNS | Service Account Key (file or JSON), or ADC | `Google_ServiceAccountKeyPath`, `Google_ServiceAccountKeyJson`, `Google_ProjectId` | -| AWS Route 53 | Access Key/Secret or IAM Role | `AwsRoute53_AccessKey`, `AwsRoute53_SecretKey` | -| Azure DNS | Client Secret or Managed Identity | `Azure_TenantId`, `Azure_ClientId`, `Azure_ClientSecret`, `Azure_SubscriptionId` | -| Cloudflare | API Token | `Cloudflare_ApiToken` | -| NS1 | API Key | `Ns1_ApiKey` | -| Infoblox | Username/Password (Basic Auth) | `Infoblox_Host`, `Infoblox_Username`, `Infoblox_Password` | -| RFC 2136 | TSIG Key (BIND) | `Rfc2136_Server`, `Rfc2136_Zone`, `Rfc2136_TsigKeyName`, `Rfc2136_TsigKey` | +🔌 Available DNS Provider Plugins + +DNS providers are distributed as **standalone plugins**, each in its own repository, and are deployed alongside the AnyCA Gateway rather than bundled into this ACME plugin. This lets you add or upgrade a DNS provider without rebuilding the ACME plugin. + +For the current, authoritative list of available DNS provider plugins, query the Keyfactor GitHub organization: + +👉 **[github.com/orgs/Keyfactor/repositories?q=-dnsplugin](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin)** + +Each plugin's own repository is the source of truth for its supported authentication methods, required configuration keys, and setup instructions. Configure the DNS provider(s) through the AnyCA Gateway's **Domain Validation** configuration; the Gateway resolves the correct plugin per domain at enrollment time.
@@ -178,133 +173,34 @@ Resolution + placement:
-🔑 Credential Flow - -Each provider supports multiple credential strategies: - -- **Google DNS**: - - ✅ **Service Account Key File** (via `Google_ServiceAccountKeyPath`) - - ✅ **Service Account Key JSON** (via `Google_ServiceAccountKeyJson` - paste JSON directly) - - ✅ **Application Default Credentials** (e.g., GCP Workload Identity or developer auth) +🔑 Provider Credentials & Configuration -- **AWS Route 53**: - - ✅ **Access/Secret Keys** (`AwsRoute53_AccessKey`, `AwsRoute53_SecretKey`) - - ✅ **IAM Role via EC2 Instance Metadata** (no explicit credentials) - -- **Azure DNS**: - - ✅ **Client Secret** (explicit `TenantId`, `ClientId`, `ClientSecret`) - - ✅ **Managed Identity** or environment-based credentials via `DefaultAzureCredential` - -- **Cloudflare**: - - ✅ **Bearer API Token** for zone-level DNS control - -- **NS1**: - - ✅ **API Key** passed in header `X-NSONE-Key` - -- **Infoblox**: - - ✅ **Username/Password** (Basic Auth via WAPI REST API) - - Optional: `Infoblox_WapiVersion` (defaults to `2.12`) - - Optional: `Infoblox_IgnoreSslErrors` for self-signed certificates - -- **RFC 2136 (BIND)**: - - ✅ **TSIG Key** for secure dynamic DNS updates - - Supports algorithms: `hmac-md5`, `hmac-sha1`, `hmac-sha256`, `hmac-sha384`, `hmac-sha512` - - Default algorithm: `hmac-sha256` (recommended) - - Optional: `Rfc2136_Port` (defaults to `53`) +Authentication methods and required configuration keys are specific to each DNS provider and are **documented in that provider's own plugin repository** — see the [`-dnsplugin` repositories](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin). Credentials and settings are supplied through the AnyCA Gateway's **Domain Validation** configuration for the chosen plugin, not in this ACME plugin's configuration.
-đŸĸ On-Premise DNS (RFC 2136) - -The RFC 2136 provider enables ACME DNS-01 challenges with on-premise DNS servers that support dynamic updates, including: - -- **BIND** (Berkeley Internet Name Domain) -- **PowerDNS** (with dynamic update support) -- Any DNS server supporting RFC 2136 with TSIG authentication - -#### Configuration Requirements +đŸĸ On-Premise / Private DNS -| Field | Description | Required | -|-------|-------------|----------| -| `Rfc2136_Server` | DNS server hostname or IP address | ✅ Yes | -| `Rfc2136_Zone` | DNS zone to update (e.g., `example.com`) | ✅ Yes | -| `Rfc2136_TsigKeyName` | TSIG key name (e.g., `acme-update-key`) | ✅ Yes | -| `Rfc2136_TsigKey` | Base64-encoded TSIG secret key | ✅ Yes | -| `Rfc2136_TsigAlgorithm` | TSIG algorithm (default: `hmac-sha256`) | Optional | -| `Rfc2136_Port` | DNS server port (default: `53`) | Optional | -| `DnsVerificationServer` | DNS server IP for verification (for private zones) | Optional | +On-premise and private DNS support (e.g. RFC 2136 dynamic updates against BIND/PowerDNS with TSIG) is provided by the corresponding DNS provider plugin — see its repository under the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) for TSIG key generation, server/zone settings, and setup examples. -#### Generating TSIG Keys - -**For BIND:** -bash - -**Generate a TSIG key using tsig-keygen (BIND 9.10+)** -tsig-keygen -a hmac-sha256 acme-update-key - -**Output example:** -key "acme-update-key" { - algorithm hmac-sha256; - secret "base64encodedkey=="; -}; - -#### BIND Configuration Example - -Add to `named.conf`: - -key "acme-update-key" { - algorithm hmac-sha256; - secret "YourBase64EncodedKeyHere=="; -}; - -zone "example.com" { - type master; - file "/var/named/example.com.zone"; - allow-update { key "acme-update-key"; }; -}; - - -> âš ī¸ **Security Note:** TSIG keys should be treated as secrets. Store them securely and use strong keys generated with cryptographically secure random number generators. - -> âš ī¸ **Private DNS Zones:** For private/local DNS zones (e.g., `.local`), set `DnsVerificationServer` to your authoritative DNS server IP so the plugin can verify TXT record propagation. +> âš ī¸ **Private DNS Zones:** For private/local DNS zones (e.g., `.local`) that are not reachable via public resolvers, set `DnsVerificationServer` to your authoritative DNS server IP. This ACME plugin uses it both to verify TXT record propagation and to resolve CNAME delegation chains.
🧩 Adding New DNS Providers -To add support for new DNS services: - -1. Implement the `IDnsProvider` interface: - ```csharp - public interface IDnsProvider - { - Task CreateRecordAsync(string recordName, string txtValue); - Task DeleteRecordAsync(string recordName); - } - ``` - -2. Register the new provider in the `DnsProviderFactory`: - ```csharp - case "yourprovider": - return new YourCustomDnsProvider(config.YourProviderConfigValues...); - ``` +DNS providers are independent plugins, so adding a new one requires **no change to this ACME plugin**. Publish a plugin that implements the AnyCA Gateway's `IDomainValidator` interface (create/cleanup the validation record for a domain), deploy it alongside the Gateway, and configure it under **Domain Validation**. The Gateway will resolve it per domain at enrollment time. -3. Use zone detection logic similar to `GoogleDnsProvider`, `AzureDnsProvider`, or `Ns1DnsProvider`. - -Each provider is instantiated dynamically based on the `DnsProvider` field in the `AcmeClientConfig`. - -> 🔁 This modular DNS system ensures challenge automation works across cloud providers and is easily extensible. +Use any existing plugin in the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) as a reference implementation.
-🔒 CA-Level DNS Provider Binding +🔒 Per-Domain DNS Provider Resolution -Each ACME/DNS combination is supported **at the CA level**, meaning that only **one DNS provider** is configured per CA entry in Keyfactor. This ensures a clear and isolated challenge path for each ACME CA connector instance. - -If you need to support multiple DNS zones/providers (e.g., both AWS and Cloudflare), configure **separate CA entries**, each with its own DNS provider configuration. +You can configure **multiple DNS provider plugins** and the Gateway selects the appropriate one for each domain based on your **Domain Validation** configuration (matching a configured, optionally wildcard, domain pattern). This also means a single certificate with SANs across different zones/providers can be validated using different plugins, and CNAME-delegated challenges are routed to the plugin that owns the delegation target's zone (see **CNAME Delegation** above).
@@ -576,13 +472,9 @@ If `AccountStoragePath` is not set and `%APPDATA%` is unavailable, the plugin de
-🌐 Google Cloud DNS in Containers - -For Google Cloud DNS in container environments, you have three authentication options: +🌐 DNS Provider Authentication in Containers -1. **Workload Identity (GKE)**: No explicit credentials needed; uses pod identity. -2. **JSON key in config**: Paste the service account JSON directly into `Google_ServiceAccountKeyJson`. -3. **Mounted JSON file**: Mount the service account key file and set `Google_ServiceAccountKeyPath`. +DNS provider credentials in containerized environments are handled by each **DNS provider plugin**, not by this ACME plugin. Options such as cloud-native identity (GKE Workload Identity, EKS IRSA, AKS Pod Identity), mounted key files, or config-supplied secrets depend on the provider — see the relevant plugin under the [`-dnsplugin` list](https://github.com/orgs/Keyfactor/repositories?q=-dnsplugin) for its supported container authentication methods.
From 389a2c493f5a9ae0256d6276aca36b6ada18f849 Mon Sep 17 00:00:00 2001 From: Brian Hill Date: Wed, 8 Jul 2026 10:52:32 -0400 Subject: [PATCH 07/10] Remove externalized DNS provider config and unused SDK dependencies DNS providers are now separate plugins, so the ACME plugin no longer needs their SDKs or per-provider config fields. Drop the AWS, Azure, ARSoft (RFC2136), and Nager.PublicSuffix package references (all unused in code) and remove the DnsProvider selector plus every per-provider config entry from the integration manifest. Keep the ACME-level fields, AccountStoragePath (still used for account storage), and DnsVerificationServer (used for propagation checks and CNAME resolution). DnsClient is retained for CNAME delegation and TXT propagation. --- AcmeCaPlugin/AcmeCaPlugin.csproj | 28 +---------- integration-manifest.json | 86 +------------------------------- 2 files changed, 3 insertions(+), 111 deletions(-) diff --git a/AcmeCaPlugin/AcmeCaPlugin.csproj b/AcmeCaPlugin/AcmeCaPlugin.csproj index 942c7ec..961da45 100644 --- a/AcmeCaPlugin/AcmeCaPlugin.csproj +++ b/AcmeCaPlugin/AcmeCaPlugin.csproj @@ -22,32 +22,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - + diff --git a/integration-manifest.json b/integration-manifest.json index 08ba468..77a7475 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -37,97 +37,13 @@ "name": "SignerEncryptionPhrase", "description": "Used to encrypt singer information when account is saved to disk (optional)" }, - { - "name": "DnsProvider", - "description": "DNS Provider to use for ACME DNS-01 challenges (options: Google, Cloudflare, AwsRoute53, Azure, Ns1, Rfc2136, Infoblox)" - }, - { - "name": "Google_ServiceAccountKeyPath", - "description": "Google Cloud DNS: Path to service account JSON key file only if using Google DNS (Optional)" - }, - { - "name": "Google_ServiceAccountKeyJson", - "description": "Google Cloud DNS: Service account JSON key content (alternative to file path for containerized deployments)" - }, - { - "name": "Google_ProjectId", - "description": "Google Cloud DNS: Project ID only if using Google DNS (Optional)" - }, { "name": "AccountStoragePath", "description": "Path for ACME account storage. Defaults to %APPDATA%\\AcmeAccounts on Windows or ./AcmeAccounts in containers." }, - { - "name": "Cloudflare_ApiToken", - "description": "Cloudflare DNS: API Token only if using Cloudflare DNS (Optional)" - }, - { - "name": "Azure_ClientId", - "description": "Azure DNS: ClientId only if using Azure DNS and Not Managed Itentity in Azure (Optional)" - }, - { - "name": "Azure_ClientSecret", - "description": "Azure DNS: ClientSecret only if using Azure DNS and Not Managed Itentity in Azure (Optional)" - }, - { - "name": "Azure_SubscriptionId", - "description": "Azure DNS: SubscriptionId only if using Azure DNS and Not Managed Itentity in Azure (Optional)" - }, - { - "name": "Azure_TenantId", - "description": "Azure DNS: TenantId only if using Azure DNS and Not Managed Itentity in Azure (Optional)" - }, - { - "name": "AwsRoute53_AccessKey", - "description": "Aws DNS: Access Key only if not using AWS DNS and default AWS Chain Creds on AWS (Optional)" - }, - { - "name": "AwsRoute53_SecretKey", - "description": "Aws DNS: Secret Key only if using AWS DNS and not using default AWS Chain Creds on AWS (Optional)" - }, - { - "name": "Ns1_ApiKey", - "description": "Ns1 DNS: Api Key only if Using Ns1 DNS (Optional)" - }, - { - "name": "Rfc2136_Server", - "description": "RFC 2136 DNS: Server hostname or IP address (Optional)" - }, - { - "name": "Rfc2136_Port", - "description": "RFC 2136 DNS: Server port (default 53) (Optional)" - }, - { - "name": "Rfc2136_Zone", - "description": "RFC 2136 DNS: Zone name (e.g., example.com) (Optional)" - }, - { - "name": "Rfc2136_TsigKeyName", - "description": "RFC 2136 DNS: TSIG key name for authentication (Optional)" - }, - { - "name": "Rfc2136_TsigKey", - "description": "RFC 2136 DNS: TSIG key (base64 encoded) for authentication (Optional)" - }, - { - "name": "Rfc2136_TsigAlgorithm", - "description": "RFC 2136 DNS: TSIG algorithm (default hmac-sha256) (Optional)" - }, { "name": "DnsVerificationServer", - "description": "DNS server to use for verifying TXT record propagation. For private/local DNS zones, set this to your authoritative DNS server IP (e.g., 10.3.10.37). Leave empty to use public DNS servers (Google, Cloudflare, etc.)." - }, - { - "name": "Infoblox_Host", - "description": "Infoblox DNS: API URL (e.g., https://infoblox.example.com/wapi/v2.12) only if using Infoblox DNS (Optional)" - }, - { - "name": "Infoblox_Username", - "description": "Infoblox DNS: Username for authentication only if using Infoblox DNS (Optional)" - }, - { - "name": "Infoblox_Password", - "description": "Infoblox DNS: Password for authentication only if using Infoblox DNS (Optional)" + "description": "DNS server used to verify TXT record propagation and to resolve CNAME delegation chains. For private/local DNS zones, set this to your authoritative DNS server IP (e.g., 10.3.10.37). Leave empty to use public DNS servers (Google, Cloudflare, etc.)." } ], "enrollment_config": [], From 3ee48076af538ddb37fd518883e7d41cd30cc1ce Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Jul 2026 14:53:21 +0000 Subject: [PATCH 08/10] docs: auto-generate README and documentation [skip ci] --- README.md | 23 +---------------------- 1 file changed, 1 insertion(+), 22 deletions(-) diff --git a/README.md b/README.md index 3cf7705..cebde3d 100644 --- a/README.md +++ b/README.md @@ -624,29 +624,8 @@ spec: * **EabKid** - External Account Binding Key ID (optional) * **EabHmacKey** - External Account Binding HMAC key (optional) * **SignerEncryptionPhrase** - Used to encrypt singer information when account is saved to disk (optional) - * **DnsProvider** - DNS Provider to use for ACME DNS-01 challenges (options: Google, Cloudflare, AwsRoute53, Azure, Ns1, Rfc2136, Infoblox) - * **Google_ServiceAccountKeyPath** - Google Cloud DNS: Path to service account JSON key file only if using Google DNS (Optional) - * **Google_ServiceAccountKeyJson** - Google Cloud DNS: Service account JSON key content (alternative to file path for containerized deployments) - * **Google_ProjectId** - Google Cloud DNS: Project ID only if using Google DNS (Optional) * **AccountStoragePath** - Path for ACME account storage. Defaults to %APPDATA%\AcmeAccounts on Windows or ./AcmeAccounts in containers. - * **Cloudflare_ApiToken** - Cloudflare DNS: API Token only if using Cloudflare DNS (Optional) - * **Azure_ClientId** - Azure DNS: ClientId only if using Azure DNS and Not Managed Itentity in Azure (Optional) - * **Azure_ClientSecret** - Azure DNS: ClientSecret only if using Azure DNS and Not Managed Itentity in Azure (Optional) - * **Azure_SubscriptionId** - Azure DNS: SubscriptionId only if using Azure DNS and Not Managed Itentity in Azure (Optional) - * **Azure_TenantId** - Azure DNS: TenantId only if using Azure DNS and Not Managed Itentity in Azure (Optional) - * **AwsRoute53_AccessKey** - Aws DNS: Access Key only if not using AWS DNS and default AWS Chain Creds on AWS (Optional) - * **AwsRoute53_SecretKey** - Aws DNS: Secret Key only if using AWS DNS and not using default AWS Chain Creds on AWS (Optional) - * **Ns1_ApiKey** - Ns1 DNS: Api Key only if Using Ns1 DNS (Optional) - * **Rfc2136_Server** - RFC 2136 DNS: Server hostname or IP address (Optional) - * **Rfc2136_Port** - RFC 2136 DNS: Server port (default 53) (Optional) - * **Rfc2136_Zone** - RFC 2136 DNS: Zone name (e.g., example.com) (Optional) - * **Rfc2136_TsigKeyName** - RFC 2136 DNS: TSIG key name for authentication (Optional) - * **Rfc2136_TsigKey** - RFC 2136 DNS: TSIG key (base64 encoded) for authentication (Optional) - * **Rfc2136_TsigAlgorithm** - RFC 2136 DNS: TSIG algorithm (default hmac-sha256) (Optional) - * **DnsVerificationServer** - DNS server to use for verifying TXT record propagation. For private/local DNS zones, set this to your authoritative DNS server IP (e.g., 10.3.10.37). Leave empty to use public DNS servers (Google, Cloudflare, etc.). - * **Infoblox_Host** - Infoblox DNS: API URL (e.g., https://infoblox.example.com/wapi/v2.12) only if using Infoblox DNS (Optional) - * **Infoblox_Username** - Infoblox DNS: Username for authentication only if using Infoblox DNS (Optional) - * **Infoblox_Password** - Infoblox DNS: Password for authentication only if using Infoblox DNS (Optional) + * **DnsVerificationServer** - DNS server used to verify TXT record propagation and to resolve CNAME delegation chains. For private/local DNS zones, set this to your authoritative DNS server IP (e.g., 10.3.10.37). Leave empty to use public DNS servers (Google, Cloudflare, etc.). 2. Define [Certificate Profiles](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/AddCP-Gateway.htm) and [Certificate Templates](https://software.keyfactor.com/Guides/AnyCAGatewayREST/Content/AnyCAGatewayREST/AddCA-Gateway.htm) for the Certificate Authority as required. One Certificate Profile must be defined per Certificate Template. It's recommended that each Certificate Profile be named after the Product ID. The Acme plugin supports the following product IDs: From fda7009c041f03c4b3d3d4d74da4c1eb12b379a9 Mon Sep 17 00:00:00 2001 From: Brian Hill Date: Wed, 8 Jul 2026 10:59:22 -0400 Subject: [PATCH 09/10] Set gateway framework compatibility to 26.2 --- docsource/configuration.md | 2 +- integration-manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docsource/configuration.md b/docsource/configuration.md index 0ad1add..9a92d95 100644 --- a/docsource/configuration.md +++ b/docsource/configuration.md @@ -51,7 +51,7 @@ The plugin uses a modular design that separates ACME communication logic and DNS ## Compatibility -The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2 and later. +The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 26.2 and later. ## Requirements diff --git a/integration-manifest.json b/integration-manifest.json index 77a7475..62482ca 100644 --- a/integration-manifest.json +++ b/integration-manifest.json @@ -9,7 +9,7 @@ "support_level": "kf-supported", "link_github": true, "update_catalog": true, - "gateway_framework": "24.2", + "gateway_framework": "26.2", "about": { "carest": { "ca_plugin_config": [ From 37f7dd2edbfce1634f30ec318e59f4e4a3134f3c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Wed, 8 Jul 2026 15:00:01 +0000 Subject: [PATCH 10/10] docs: auto-generate README and documentation [skip ci] --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index cebde3d..f9282ad 100644 --- a/README.md +++ b/README.md @@ -85,7 +85,7 @@ The plugin uses a modular design that separates ACME communication logic and DNS ## Compatibility -The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2 and later. +The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 26.2 and later. ## Support The Acme AnyCA Gateway REST plugin is supported by Keyfactor for Keyfactor customers. If you have a support issue, please open a support ticket via the Keyfactor Support Portal at https://support.keyfactor.com. @@ -635,7 +635,7 @@ spec: ## Compatibility -The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 24.2 and later. +The Acme AnyCA Gateway REST plugin is compatible with the Keyfactor AnyCA Gateway REST 26.2 and later. ## License