v3.0.0: authenticate with a yk_ App key, not email/app_password

The email+app_password -> /api/v1/auth/login bearer mint was retired
with personal app passwords (dns commit 834c90e). Switch to sending a
yeil App key (yk_<keyId>_<secret>) directly as the Bearer token, which
the DNS API's principal auth accepts. Single credential 'dns_yeil_api_key';
removed the login round-trip. BREAKING: existing credential files must
replace email/app_password with an api_key (an App with DNS record-write
permission, minted in team Apps). README + version bumped.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
eskimo
2026-06-21 21:08:30 -04:00
parent a7f781e151
commit e6d9e17d1e
3 changed files with 35 additions and 40 deletions

View File

@@ -1,9 +1,9 @@
"""DNS-01 authenticator plugin for Certbot using the yeil public API.
Authenticates against dns.yeil.app/api/v1/auth/login with an
email + app password, caches the Bearer token for the run, then
adds/removes TXT records via the public records API. Any yeil user
with an app password and an owned DNS zone can use it.
Authenticates to dns.yeil.app with a yeil App key (yk_...) sent as a
Bearer token, then adds/removes TXT records via the public records API.
Create an App with DNS record-write permission on your zone(s) in your
yeil team settings (the Apps tab) and put its key in the credentials file.
The certbot host only needs HTTPS reachability to dns.yeil.app; no
NetBird or shared admin key.
@@ -34,10 +34,6 @@ class Authenticator(dns_common.DNSAuthenticator):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.credentials = None
# Bearer token cached for the lifetime of this plugin instance.
# The login route mints a 30-day session; we only need it for
# the duration of one certbot run.
self._token = None
# (domain, validation_name, validation) -> (zone_id, record_id)
self._records = {}
@@ -59,8 +55,10 @@ class Authenticator(dns_common.DNSAuthenticator):
"credentials",
"yeil API credentials INI file",
{
"email": "yeil account email (e.g. you@yourdomain.com)",
"app_password": "yeil app password (create one in account.yeil.app/security)",
"api_key": (
"yeil App key (yk_...) with DNS record-write permission on "
"your zone(s); create an App under Apps in your team settings"
),
},
)
@@ -85,9 +83,7 @@ class Authenticator(dns_common.DNSAuthenticator):
req.add_header("Content-Type", "application/json")
req.add_header("Content-Length", str(len(data)))
if auth:
if not self._token:
self._login()
req.add_header("Authorization", f"Bearer {self._token}")
req.add_header("Authorization", f"Bearer {self._api_key()}")
try:
with urlopen(req, timeout=HTTP_TIMEOUT) as resp:
@@ -114,20 +110,14 @@ class Authenticator(dns_common.DNSAuthenticator):
f"yeil dns API error ({method} {path}, {e.code}): {msg}"
)
def _login(self):
email = self.credentials.conf("email")
password = self.credentials.conf("app_password")
result = self._request(
"POST",
"/api/v1/auth/login",
body={"email": email, "password": password},
auth=False,
)
if not isinstance(result, dict) or "token" not in result:
def _api_key(self):
key = self.credentials.conf("api_key")
if not key:
raise errors.PluginError(
"yeil dns API login returned no token"
"yeil credentials file is missing 'api_key' "
"(a yk_... App key with DNS record-write permission)"
)
self._token = result["token"]
return key
# ── Zone resolution ────────────────────────────────────────────────