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:
33
README.md
33
README.md
@@ -2,10 +2,11 @@
|
||||
|
||||
yeil DNS Authenticator plugin for [Certbot](https://certbot.eff.org/).
|
||||
|
||||
Authenticates against `dns.yeil.app`'s public API with an email and an
|
||||
app password, then adds/removes TXT records to satisfy ACME DNS-01
|
||||
challenges. Works for any yeil user with an owned DNS zone; the
|
||||
certbot host just needs HTTPS reachability to `dns.yeil.app`.
|
||||
Authenticates to `dns.yeil.app`'s public API with a yeil **App key**
|
||||
(`yk_...`) sent as a Bearer token, then adds/removes TXT records to
|
||||
satisfy ACME DNS-01 challenges. Works for any yeil team with an App that
|
||||
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
|
||||
authenticator) is needed for `*.example.com`.
|
||||
@@ -18,16 +19,20 @@ pip install git+https://git.eskimo.dev/Yeil/certbot-dns-yeil.git
|
||||
|
||||
## Configuration
|
||||
|
||||
Create an app password at `https://account.yeil.app/security` and
|
||||
drop it into a credentials INI:
|
||||
In your yeil team settings, open **Apps**, create an App, grant it DNS
|
||||
**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
|
||||
dns_yeil_email = you@yourdomain.com
|
||||
dns_yeil_app_password = abcd-efgh-ijkl-mnop
|
||||
dns_yeil_api_key = yk_xxxxxxxx_yyyyyyyyyyyyyyyyyyyyyyyy
|
||||
```
|
||||
|
||||
`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:
|
||||
|
||||
```ini
|
||||
@@ -55,13 +60,13 @@ certbot certonly \
|
||||
|
||||
## How it works
|
||||
|
||||
The plugin logs in once per run (`POST /api/v1/auth/login`) and caches
|
||||
the returned Bearer token. For each requested name it asks the API
|
||||
which zone the account owns that covers the FQDN
|
||||
(`GET /api/v1/zones?suffix_of=<fqdn>`), creates a TXT at
|
||||
The plugin sends the App key as a Bearer token on every request. For
|
||||
each requested name it asks the API which of the App's zones covers the
|
||||
FQDN (`GET /api/v1/zones?suffix_of=<fqdn>`), creates a TXT at
|
||||
`_acme-challenge.<rel>` (`POST /api/v1/zones/{id}/records`), waits for
|
||||
propagation, and on cleanup deletes the record by id
|
||||
(`DELETE /api/v1/zones/{id}/records/{recordId}`).
|
||||
|
||||
The token is a real yeil session; revoking the app password (or
|
||||
hitting `/logout`) invalidates it cleanly.
|
||||
Revoking the App key (or disabling the App) in your team settings cuts
|
||||
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.
|
||||
|
||||
@@ -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 ────────────────────────────────────────────────
|
||||
|
||||
|
||||
Reference in New Issue
Block a user