pokbun API does not work, leaks secret key in debug logs
lunik1 opened this issue · 3 comments
I am running the latest head (7d576c4) and trying to update the DNS records of a domain whose DNS is managed by porkbun.
This config fails to update the DNS records:
provider porkbun.com {
username = <apikey>
secretapikey = <secretapikey>
hostname = <my_hostname>
}
Debug log error:
HTTP 404: Unexpected status code.
Zone '<my_hostname>' not found.
Error response from DDNS server, exiting!
Error code 48: DDNS server response not OK
After looking into the code there seem to be a few issues with it:
Firstly, the example configuration seems to be wrong
provider porkbun.com {
checkip-server = checkip.porkbun.com
username = zone.com
password = api_token #Create a unique custom api token with the following permissions: Zone.Zone - Read, Zone.DNS - Edit.
hostname = yourhostname.yourdomain.com
ttl = 400 #optional, by default is 300 seconds
}
When an API token is created with porkbun there does not seem to be a way to restrict its permissions. Also, an API token consists of both an API key and a Secret API Key: in the code the username and password are used for these respectively, but this is not reflected in the example. I have tried setting the username to my domain name, as in the example, but I get the same issue.
The code seems to use the wrong url for the porkbun API: api.porkbun.com/client/v4
. The official documentation uses porkbun.com/api/json/v3/
- there is no API v4 as far as I can make out. I suspect this is the cause of the issue as this api.porkbun.com/client/v4
address does not seem to work using curl in the command line while porkbun.com/api/json/v3/
does (although I haven't been able to reproduce exactly, I get stuck in a redirect loop rather than hitting a 404).
Finally, with debug logging enabled, the code will print the secret API key when making a request. This should be censored or there should be a warning that debug logging can contain sensitive information.
Thanks for the Analysis.
Have you tried to Change the endpoint URL in porkbun.c?
Well, I have tried it now. It is not that simple:
GET /api/json/v3/dns/retrieve/x.name HTTP/1.0
Host: porkbun.com
User-Agent: inadyn/2.12.0 https://github.com/troglobit/inadyn/issues
Accept: */*
Content-Type: application/json
Content-Length: 68
inadyn[3046142]: Successfully sent HTTPS request!
inadyn[3046142]: Successfully received HTTPS response (912/8191 bytes)!
inadyn[3046142]: Response:
HTTP/1.1 400 Method Not Allowed
Date: Sat, 25 May 2024 07:18:17 GMT
Content-Type: application/json
Connection: close
Server: openresty
Expires: Thu, 19 Nov 1981 08:52:00 GMT
Cache-Control: no-store, no-cache, must-revalidate
Pragma: no-cache
Strict-Transport-Security: max-age=63072000; includeSubDomains; preload
{"status":"ERROR","message":"All HTTP request must use POST."}
Changing it to GET in porkbun.c gives me
{"status":"ERROR","message":"All API requests must provide minimal required data."}
Which is pretty odd, as the only data required is the api-keys - which are provided apparently in the json.
Here my changes up to now:
-the first two lines
-post instead of get
#define API_HOST "porkbun.com"
#define API_URL "/api/json/v3"
/* https://kb.porkbun.com/article/190-getting-started-with-the-porkbun-api */
static const char *PORKBUN_ZONE_ID_REQUEST = "POST " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
"Host: " API_HOST "\r\n" \
"User-Agent: %s\r\n" \
"Accept: */*\r\n" \
"Content-Type: application/json\r\n" \
"Content-Length: %zd\r\n\r\n" \
"{\"apikey\":\"%s\",\"secretapikey\":\"%s\"}";
static const char *PORKBUN_HOSTNAME_ID_REQUEST_BY_NAME = "POST " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
"Host: " API_HOST "\r\n" \
"User-Agent: %s\r\n" \
"Accept: */*\r\n" \
"Content-Type: application/json\r\n" \
"Content-Length: %zd\r\n\r\n" \
"{\"apikey\":\"%s\",\"secretapikey\":\"%s\"}";
The complete file https://paste.debian.net/hidden/1c957794/
The error @henfri is because the Content-Length is not being calculated properly. I haven't gotten this to work, but here is a summary of what I found (including henfri's prior work):
- Correct API_URL and HTTP Method from GET to POST
#define CHECK(fn) { rc = (fn); if (rc) goto cleanup; }
#define API_HOST "api.porkbun.com"
-#define API_URL "/client/v4"
+#define API_URL "/api/json/v3"
/* https://kb.porkbun.com/article/190-getting-started-with-the-porkbun-api */
-static const char *PORKBUN_ZONE_ID_REQUEST = "GET " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
+static const char *PORKBUN_ZONE_ID_REQUEST = "POST " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
"Host: " API_HOST "\r\n" \
"User-Agent: %s\r\n" \
"Accept: */*\r\n" \
@@ -36,7 +36,7 @@ static const char *PORKBUN_ZONE_ID_REQUEST = "GET " API_URL "/dns/retrieve/%s HT
"Content-Length: %zd\r\n\r\n" \
"{\"apikey\":\"%s\",\"secretapikey\":\"%s\"}";
-static const char *PORKBUN_HOSTNAME_ID_REQUEST_BY_NAME = "GET " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
+static const char *PORKBUN_HOSTNAME_ID_REQUEST_BY_NAME = "POST " API_URL "/dns/retrieve/%s HTTP/1.0\r\n" \
"Host: " API_HOST "\r\n" \
"User-Agent: %s\r\n" \
"Accept: */*\r\n" \
- Correct SUCCESS token case
@@ -123,8 +123,10 @@ static int check_success(const char *json, const jsmntok_t tokens[], const int n
for (i = 1; i < num_tokens; i++) {
int set;
- if (jsoneq(json, tokens + i, "success") != 0)
+ if (jsoneq(json, tokens + i, "SUCCESS") != 0) {
- Correct calculation of Content-Length in setup API call. Originally, POST body length was set to len(api key), but the correct length is len({"apikey":"$API_KEY","secretapikey":,"$SECRET_API_KEY"}).
@@ -294,7 +304,7 @@ static int setup(ddns_t *ctx, ddns_info_t *info, ddns_alias_t *hostname)
PORKBUN_ZONE_ID_REQUEST,
zone_name,
info->user_agent,
- strlen(info->creds.username),
+ strlen("{\"apikey\":\"") + strlen(info->creds.username) + strlen("\",\"secretapikey\":\"") + strlen(info->creds.password) + strlen("\"}"),
info->creds.username,
info->creds.password);