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

@@ -2,10 +2,11 @@
yeil DNS Authenticator plugin for [Certbot](https://certbot.eff.org/). yeil DNS Authenticator plugin for [Certbot](https://certbot.eff.org/).
Authenticates against `dns.yeil.app`'s public API with an email and an Authenticates to `dns.yeil.app`'s public API with a yeil **App key**
app password, then adds/removes TXT records to satisfy ACME DNS-01 (`yk_...`) sent as a Bearer token, then adds/removes TXT records to
challenges. Works for any yeil user with an owned DNS zone; the satisfy ACME DNS-01 challenges. Works for any yeil team with an App that
certbot host just needs HTTPS reachability to `dns.yeil.app`. has DNS record-write permission; the certbot host just needs HTTPS
reachability to `dns.yeil.app`.
Wildcard certs require DNS-01, so this plugin (or another DNS Wildcard certs require DNS-01, so this plugin (or another DNS
authenticator) is needed for `*.example.com`. authenticator) is needed for `*.example.com`.
@@ -18,16 +19,20 @@ pip install git+https://git.eskimo.dev/Yeil/certbot-dns-yeil.git
## Configuration ## Configuration
Create an app password at `https://account.yeil.app/security` and In your yeil team settings, open **Apps**, create an App, grant it DNS
drop it into a credentials INI: **record-write** permission on the zone(s) you'll issue certs for, and
mint a key. Drop the key (`yk_...`) into a credentials INI:
```ini ```ini
dns_yeil_email = you@yourdomain.com dns_yeil_api_key = yk_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy
dns_yeil_app_password = abcd-efgh-ijkl-mnop
``` ```
`chmod 600` it. `chmod 600` it.
> Migrating from 2.x: the old `dns_yeil_email` / `dns_yeil_app_password`
> login was retired with personal app passwords. Replace those two lines
> with a single `dns_yeil_api_key`.
Optional override if you're testing against a non-production host: Optional override if you're testing against a non-production host:
```ini ```ini
@@ -55,13 +60,13 @@ certbot certonly \
## How it works ## How it works
The plugin logs in once per run (`POST /api/v1/auth/login`) and caches The plugin sends the App key as a Bearer token on every request. For
the returned Bearer token. For each requested name it asks the API each requested name it asks the API which of the App's zones covers the
which zone the account owns that covers the FQDN FQDN (`GET /api/v1/zones?suffix_of=<fqdn>`), creates a TXT at
(`GET /api/v1/zones?suffix_of=<fqdn>`), creates a TXT at
`_acme-challenge.<rel>` (`POST /api/v1/zones/{id}/records`), waits for `_acme-challenge.<rel>` (`POST /api/v1/zones/{id}/records`), waits for
propagation, and on cleanup deletes the record by id propagation, and on cleanup deletes the record by id
(`DELETE /api/v1/zones/{id}/records/{recordId}`). (`DELETE /api/v1/zones/{id}/records/{recordId}`).
The token is a real yeil session; revoking the app password (or Revoking the App key (or disabling the App) in your team settings cuts
hitting `/logout`) invalidates it cleanly. off access cleanly. The key only carries the DNS permissions you granted
the App, so scope it to record-write on just the zones you need.

View File

@@ -1,9 +1,9 @@
"""DNS-01 authenticator plugin for Certbot using the yeil public API. """DNS-01 authenticator plugin for Certbot using the yeil public API.
Authenticates against dns.yeil.app/api/v1/auth/login with an Authenticates to dns.yeil.app with a yeil App key (yk_...) sent as a
email + app password, caches the Bearer token for the run, then Bearer token, then adds/removes TXT records via the public records API.
adds/removes TXT records via the public records API. Any yeil user Create an App with DNS record-write permission on your zone(s) in your
with an app password and an owned DNS zone can use it. 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 The certbot host only needs HTTPS reachability to dns.yeil.app; no
NetBird or shared admin key. NetBird or shared admin key.
@@ -34,10 +34,6 @@ class Authenticator(dns_common.DNSAuthenticator):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self.credentials = None 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) # (domain, validation_name, validation) -> (zone_id, record_id)
self._records = {} self._records = {}
@@ -59,8 +55,10 @@ class Authenticator(dns_common.DNSAuthenticator):
"credentials", "credentials",
"yeil API credentials INI file", "yeil API credentials INI file",
{ {
"email": "yeil account email (e.g. you@yourdomain.com)", "api_key": (
"app_password": "yeil app password (create one in account.yeil.app/security)", "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-Type", "application/json")
req.add_header("Content-Length", str(len(data))) req.add_header("Content-Length", str(len(data)))
if auth: if auth:
if not self._token: req.add_header("Authorization", f"Bearer {self._api_key()}")
self._login()
req.add_header("Authorization", f"Bearer {self._token}")
try: try:
with urlopen(req, timeout=HTTP_TIMEOUT) as resp: 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}" f"yeil dns API error ({method} {path}, {e.code}): {msg}"
) )
def _login(self): def _api_key(self):
email = self.credentials.conf("email") key = self.credentials.conf("api_key")
password = self.credentials.conf("app_password") if not key:
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:
raise errors.PluginError( 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 ──────────────────────────────────────────────── # ── Zone resolution ────────────────────────────────────────────────

View File

@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup( setup(
name="certbot-dns-yeil", name="certbot-dns-yeil",
version="2.0.0", version="3.0.0",
description="yeil DNS Authenticator plugin for Certbot", description="yeil DNS Authenticator plugin for Certbot",
url="https://git.eskimo.dev/Yeil/certbot-dns-yeil", url="https://git.eskimo.dev/Yeil/certbot-dns-yeil",
author="yeil", author="yeil",