Repository: go-acme/lego
Pull request #2019 (2023-09-19 08:57:35)

pdns: optional custom API version
Some PowerDNS servers don't have exposed the [root API endpoints](https://doc.powerdns.com/recursor/common/api/endpoint-api.html) and PowerDNS API version auto-detection fails. Use the optional parameter `PDNS_CUSTOM_API_VERSION` to set the version number in such situations.

related to  #2016

ldez: Do we agree that I close the issue and flag it as "question" and you still created a PR?
What exactly do you expect from this behavior?
m0t1x:  Hi,
As described in the PR, PowerDNS has root API endpoints to provide the version information (https://doc.powerdns.com/recursor/common/api/endpoint-api.html). Some PowerDNS deployment don't have these endpoints exposed and without them it is not possible to auto-detect PowerDNS API version and the whole process with lego fails. Using custom variable to set the API version, you can skip the API version auto-detection and the process will continue.
I found I am not the only one with this issue, but it's up to you to see whether you want to have this feature or not.
Best Regards,Marijan




    On Wednesday, October 25, 2023 at 01:15:58 AM GMT+2, Ludovic Fernandez ***@***.***> wrote:  
 
 


Do we agree that I close the issue and flag it as "question" and you still created a PR?
What exactly do you expect from this behavior?

—
Reply to this email directly, view it on GitHub, or unsubscribe.
You are receiving this because you authored the thread.Message ID: ***@***.***>
  
7c: I agree, powerdns 4.* does not have this endpoint, so i had to hack into the code in order to set the apiVersion to 1

Patch:
diff --git a/cmd/zz_gen_cmd_dnshelp.go b/cmd/zz_gen_cmd_dnshelp.go
index 24245e02..f28ef2e6 100644
--- a/cmd/zz_gen_cmd_dnshelp.go
+++ b/cmd/zz_gen_cmd_dnshelp.go
@@ -2078,6 +2078,7 @@ func displayDNSHelp(w io.Writer, name string) error {
 		ew.writeln()
 
 		ew.writeln(`Additional Configuration:`)
+		ew.writeln(`	- "PDNS_API_VERSION":	Skip API version autodetection and use the provided version number.`)
 		ew.writeln(`	- "PDNS_HTTP_TIMEOUT":	API request timeout`)
 		ew.writeln(`	- "PDNS_POLLING_INTERVAL":	Time between DNS propagation check`)
 		ew.writeln(`	- "PDNS_PROPAGATION_TIMEOUT":	Maximum waiting time for DNS propagation`)
diff --git a/docs/content/dns/zz_gen_pdns.md b/docs/content/dns/zz_gen_pdns.md
index c6febaaf..22a730b0 100644
--- a/docs/content/dns/zz_gen_pdns.md
+++ b/docs/content/dns/zz_gen_pdns.md
@@ -49,6 +49,7 @@ More information [here]({{< ref "dns#configuration-and-credentials" >}}).
 
 | Environment Variable Name | Description |
 |--------------------------------|-------------|
+| `PDNS_API_VERSION` | Skip API version autodetection and use the provided version number. |
 | `PDNS_HTTP_TIMEOUT` | API request timeout |
 | `PDNS_POLLING_INTERVAL` | Time between DNS propagation check |
 | `PDNS_PROPAGATION_TIMEOUT` | Maximum waiting time for DNS propagation |
@@ -65,6 +66,7 @@ Tested and confirmed to work with PowerDNS authoritative server 3.4.8 and 4.0.1.
 PowerDNS Notes:
 - PowerDNS API does not currently support SSL, therefore you should take care to ensure that traffic between lego and the PowerDNS API is over a trusted network, VPN etc.
 - In order to have the SOA serial automatically increment each time the `_acme-challenge` record is added/modified via the API, set `SOA-EDIT-API` to `INCEPTION-INCREMENT` for the zone in the `domainmetadata` table
+- Some PowerDNS servers doesn't have root API endpoints enabled and API version autodetection will not work. In that case version number can be defined using `PDNS_API_VERSION`.
 
 
 
diff --git a/docs/data/zz_cli_help.toml b/docs/data/zz_cli_help.toml
index e3b598b0..a877121b 100644
--- a/docs/data/zz_cli_help.toml
+++ b/docs/data/zz_cli_help.toml
@@ -82,7 +82,7 @@ USAGE:
 
 OPTIONS:
    --days value                              The number of days left on a certificate to renew it. (default: 0)
-   --ari-enable                              Use the renewalInfo endpoint (draft-ietf-acme-ari-02) to check if a certificate should be renewed. (default: false)
+   --ari-enable                              Use the renewalInfo endpoint (draft-ietf-acme-ari) to check if a certificate should be renewed. (default: false)
    --ari-wait-to-renew-duration value        The maximum duration you're willing to sleep for a renewal time returned by the renewalInfo endpoint. (default: 0s)
    --reuse-key                               Used to indicate you want to reuse your current private key for the new certificate. (default: false)
    --no-bundle                               Do not create a certificate bundle by adding the issuers certificate to the new certificate. (default: false)
diff --git a/providers/dns/pdns/internal/client.go b/providers/dns/pdns/internal/client.go
index d7b247a4..7eed57e3 100644
--- a/providers/dns/pdns/internal/client.go
+++ b/providers/dns/pdns/internal/client.go
@@ -30,10 +30,11 @@ type Client struct {
 }
 
 // NewClient creates a new Client.
-func NewClient(host *url.URL, serverName string, apiKey string) *Client {
+func NewClient(host *url.URL, serverName string, apiVersion int, apiKey string) *Client {
 	return &Client{
 		serverName: serverName,
 		apiKey:     apiKey,
+		apiVersion: apiVersion,
 		Host:       host,
 		HTTPClient: &http.Client{Timeout: 5 * time.Second},
 	}
diff --git a/providers/dns/pdns/pdns.go b/providers/dns/pdns/pdns.go
index 5962fddc..f3d3e2b8 100644
--- a/providers/dns/pdns/pdns.go
+++ b/providers/dns/pdns/pdns.go
@@ -23,6 +23,7 @@ const (
 	EnvAPIURL = envNamespace + "API_URL"
 
 	EnvTTL                = envNamespace + "TTL"
+	EnvAPIVersion         = envNamespace + "API_VERSION"
 	EnvPropagationTimeout = envNamespace + "PROPAGATION_TIMEOUT"
 	EnvPollingInterval    = envNamespace + "POLLING_INTERVAL"
 	EnvHTTPTimeout        = envNamespace + "HTTP_TIMEOUT"
@@ -34,6 +35,7 @@ type Config struct {
 	APIKey             string
 	Host               *url.URL
 	ServerName         string
+	APIVersion         int
 	PropagationTimeout time.Duration
 	PollingInterval    time.Duration
 	TTL                int
@@ -43,10 +45,11 @@ type Config struct {
 // NewDefaultConfig returns a default configuration for the DNSProvider.
 func NewDefaultConfig() *Config {
 	return &Config{
+		ServerName:         env.GetOrDefaultString(EnvServerName, "localhost"),
+		APIVersion:         env.GetOrDefaultInt(EnvAPIVersion, 0),
 		TTL:                env.GetOrDefaultInt(EnvTTL, dns01.DefaultTTL),
 		PropagationTimeout: env.GetOrDefaultSecond(EnvPropagationTimeout, 120*time.Second),
 		PollingInterval:    env.GetOrDefaultSecond(EnvPollingInterval, 2*time.Second),
-		ServerName:         env.GetOrDefaultString(EnvServerName, "localhost"),
 		HTTPClient: &http.Client{
 			Timeout: env.GetOrDefaultSecond(EnvHTTPTimeout, 30*time.Second),
 		},
@@ -94,11 +97,13 @@ func NewDNSProviderConfig(config *Config) (*DNSProvider, error) {
 		return nil, errors.New("pdns: API URL missing")
 	}
 
-	client := internal.NewClient(config.Host, config.ServerName, config.APIKey)
+	client := internal.NewClient(config.Host, config.ServerName, config.APIVersion, config.APIKey)
 
-	err := client.SetAPIVersion(context.Background())
-	if err != nil {
-		log.Warnf("pdns: failed to get API version %v", err)
+	if config.APIVersion <= 0 {
+		err := client.SetAPIVersion(context.Background())
+		if err != nil {
+			log.Warnf("pdns: failed to get API version %v", err)
+		}
 	}
 
 	return &DNSProvider{config: config, client: client}, nil
diff --git a/providers/dns/pdns/pdns.toml b/providers/dns/pdns/pdns.toml
index f1209e4a..a59c02cd 100644
--- a/providers/dns/pdns/pdns.toml
+++ b/providers/dns/pdns/pdns.toml
@@ -18,6 +18,7 @@ Tested and confirmed to work with PowerDNS authoritative server 3.4.8 and 4.0.1.
 PowerDNS Notes:
 - PowerDNS API does not currently support SSL, therefore you should take care to ensure that traffic between lego and the PowerDNS API is over a trusted network, VPN etc.
 - In order to have the SOA serial automatically increment each time the `_acme-challenge` record is added/modified via the API, set `SOA-EDIT-API` to `INCEPTION-INCREMENT` for the zone in the `domainmetadata` table
+- Some PowerDNS servers doesn't have root API endpoints enabled and API version autodetection will not work. In that case version number can be defined using `PDNS_API_VERSION`.
 '''
 
 [Configuration]
@@ -25,11 +26,12 @@ PowerDNS Notes:
     PDNS_API_KEY = "API key"
     PDNS_API_URL = "API URL"
   [Configuration.Additional]
+    PDNS_SERVER_NAME = "Name of the server in the URL, 'localhost' by default"
+    PDNS_API_VERSION = "Skip API version autodetection and use the provided version number."
     PDNS_POLLING_INTERVAL = "Time between DNS propagation check"
     PDNS_PROPAGATION_TIMEOUT = "Maximum waiting time for DNS propagation"
     PDNS_TTL = "The TTL of the TXT record used for the DNS challenge"
     PDNS_HTTP_TIMEOUT = "API request timeout"
-    PDNS_SERVER_NAME = "Name of the server in the URL, 'localhost' by default"
 
 [Links]
   API = "https://doc.powerdns.com/md/httpapi/README/"


Test patch:
diff --git a/providers/dns/pdns/internal/client_test.go b/providers/dns/pdns/internal/client_test.go
index d102a5ef..4e17a4fd 100644
--- a/providers/dns/pdns/internal/client_test.go
+++ b/providers/dns/pdns/internal/client_test.go
@@ -57,7 +57,7 @@ func setupTest(t *testing.T, method, pattern string, status int, file string) *C
 
 	serverURL, _ := url.Parse(server.URL)
 
-	client := NewClient(serverURL, "server", "secret")
+	client := NewClient(serverURL, "server", 0, "secret")
 	client.HTTPClient = server.Client()
 
 	return client
@@ -151,8 +151,7 @@ func TestClient_joinPath(t *testing.T) {
 			host, err := url.Parse(test.baseURL)
 			require.NoError(t, err)
 
-			client := NewClient(host, "test", "secret")
-			client.apiVersion = test.apiVersion
+			client := NewClient(host, "test", test.apiVersion, "secret")
 
 			endpoint := client.joinPath(test.uri)
 
diff --git a/providers/dns/pdns/pdns_test.go b/providers/dns/pdns/pdns_test.go
index 854b159d..70b386b8 100644
--- a/providers/dns/pdns/pdns_test.go
+++ b/providers/dns/pdns/pdns_test.go
@@ -76,30 +76,31 @@ func TestNewDNSProvider(t *testing.T) {
 
 func TestNewDNSProviderConfig(t *testing.T) {
 	testCases := []struct {
-		desc     string
-		apiKey   string
-		host     *url.URL
-		expected string
+		desc             string
+		apiKey           string
+		customAPIVersion int
+		host             *url.URL
+		expected         string
 	}{
 		{
 			desc:   "success",
 			apiKey: "123",
-			host: func() *url.URL {
-				u, _ := url.Parse("http://example.com")
-				return u
-			}(),
+			host:   mustParse("http://example.com"),
+		},
+		{
+			desc:             "success custom API version",
+			apiKey:           "123",
+			customAPIVersion: 1,
+			host:             mustParse("http://example.com"),
 		},
 		{
 			desc:     "missing credentials",
 			expected: "pdns: API key missing",
 		},
 		{
-			desc:   "missing API key",
-			apiKey: "",
-			host: func() *url.URL {
-				u, _ := url.Parse("http://example.com")
-				return u
-			}(),
+			desc:     "missing API key",
+			apiKey:   "",
+			host:     mustParse("http://example.com"),
 			expected: "pdns: API key missing",
 		},
 		{
@@ -114,6 +115,7 @@ func TestNewDNSProviderConfig(t *testing.T) {
 			config := NewDefaultConfig()
 			config.APIKey = test.apiKey
 			config.Host = test.host
+			config.APIVersion = test.customAPIVersion
 
 			p, err := NewDNSProviderConfig(config)
 
@@ -143,3 +145,11 @@ func TestLivePresentAndCleanup(t *testing.T) {
 	err = provider.CleanUp(envTest.GetDomain(), "", "123d==")
 	require.NoError(t, err)
 }
+
+func mustParse(rawURL string) *url.URL {
+	u, err := url.Parse(rawURL)
+	if err != nil {
+		panic(err)
+	}
+	return u
+}