From da48d8ef903059637753c1a1a637f9864d5a6cd2 Mon Sep 17 00:00:00 2001 From: Sven Luthi Date: Tue, 27 Oct 2026 11:59:33 +0100 Subject: [PATCH] modified: README.md modified: certbot_dns_netbox/dns_netbox.py --- README.md | 149 +++++++++++++++++++++++++++---- certbot_dns_netbox/dns_netbox.py | 2 +- 2 files changed, 131 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index b6214b3..2455322 100644 --- a/README.md +++ b/README.md @@ -20,23 +20,6 @@ and the plugin **never creates or deletes records**: 3. leaves the records in place after validation so they can be updated again on the next renewal. -### Provisioning - -The challenge TXT records must be created beforehand in netbox-dns, in the -view configured via `search_view` (`certbot_dns_netbox/dns_netbox.py`, -default: `global`). Each challenge token needs its own record: - -| Certificate request | TXT records needed | -| ------------------------------------------------------ | ------------------ | -| `-d example.com` or `-d '*.example.com'` | 1 | -| `-d example.com -d '*.example.com'` (apex + wildcard) | 2 | - -All records for a challenge share the same name (e.g. name -`_acme-challenge.unifi` in zone `ewcs.ch`); netbox-dns allows multiple TXT -records with different values for the same name. If a certificate request -needs more records than exist, certbot aborts with an error. Renewals simply -rewrite the values, so the records never need to be touched again. - Connection settings are taken from the environment, exactly as `nbapi.py` expects them: @@ -51,12 +34,140 @@ One of `NETBOX_BEARER` / `NETBOX_TOKEN` is required. The token's user needs NetBox object permissions to view and change netbox-dns records only — no zone or view permissions are required. +## Tutorial: end-to-end setup + +This walkthrough sets up certificates for `example.com`, including the +wildcard, from scratch. Adapt domains, names and paths to your environment. + +### 1. Create the DNS zone + +Create the `example.com` zone in netbox-dns, in the view the plugin searches +(`search_view` in `certbot_dns_netbox/dns_netbox.py`, default: `_default_`), and +note the numeric **zone ID** of the new zone — it is shown in the web UI URL +and can be looked up with: + +``` +GET /api/plugins/netbox-dns/zones/?name=example.com +``` + +### 2. Create a NetBox permission + +In the NetBox admin interface, create a permission named +`ACME Client - Zone example.com`: + +* **Object type**: `DNS › record` only +* **Actions**: view and change — nothing else. The plugin never creates or + deletes records, and it does not need zone or view permissions. +* **Constraint**: + +```json +{"name": "_acme-challenge", "type": "TXT", "zone_id": 0000} +``` + +`0000` is the zone ID from step 1. The constraint limits the token to TXT +records named `_acme-challenge` in exactly that zone. Note that certificates +for sub-domains (`-d sub.example.com`) use a record named +`_acme-challenge.sub`; in that case, relax the constraint to +`"name__startswith": "_acme-challenge"` instead of the exact `name`. + +### 3. Create a service user + +* Create a user group, e.g. `ACME Clients`, and assign the permission from + step 2 to the **group** — any service user added to the group later + inherits it. +* Create a user, e.g. `certbot`, without a usable login, and make it a member + of the group. + +### 4. Pre-provision the TXT records + +The plugin only updates records, so they must exist before the first run. +Create `_acme-challenge` TXT records in the `example.com` zone — one per +challenge token: + +| Certificate request | TXT records needed | +| ----------------------------------------------------- | ------------------ | +| `-d example.com` or `-d '*.example.com'` | 1 | +| `-d example.com -d '*.example.com'` (apex + wildcard) | 2 | + +All records for a challenge share the same name (`_acme-challenge`, relative +to the zone); netbox-dns allows multiple TXT records with different values +for the same name. Initial values are placeholders and get rewritten by the +plugin. If a certificate request needs more records than exist, certbot +aborts with `Not enough TXT records ...` — add another record and rerun. + +### 5. Create an API token and environment file + +On the `certbot` user, create an API token (leave write enabled) and expose +it to whatever runs certbot — a cronjob in most cases — via environment +variables. Keep them in a root-readable-only file, e.g. +`/etc/default/certbot-netbox`: + +```sh +NETBOX_URL=https://netbox.example.com +NETBOX_TOKEN= # or: NETBOX_BEARER= +``` + +``` +chmod 600 /etc/default/certbot-netbox +``` + +### 6. Install certbot and the plugin in a dedicated venv + +Do not install certbot from the system package manager — certbot and the +plugin live together in a dedicated venv: + +```sh +python3 -m venv /opt/certbot-netbox +/opt/certbot-netbox/bin/pip install certbot certbot-dns-netbox +``` + +### 7. Test + +```sh +/opt/certbot-netbox/bin/certbot certonly \ + --authenticator dns-netbox \ + -d example.com -d '*.example.com' +``` + +The first ever certbot run asks for an ACME account e-mail and the terms of +service. During the run the plugin writes the challenge tokens into the +`_acme-challenge` TXT records; the records remain in place afterwards. Tip: +append `--dry-run` for a first try to validate the setup against the staging +ACME API without touching rate limits. + +### 8. Automate renewal + +Create a wrapper that loads the environment and runs certbot from the venv, +e.g. `/usr/local/bin/certbot-netbox-renew`: + +```sh +#!/bin/sh +set -a +. /etc/default/certbot-netbox +set +a +exec /opt/certbot-netbox/bin/certbot renew --quiet +``` + +``` +chmod +x /usr/local/bin/certbot-netbox-renew +``` + +And a cron entry, e.g. `/etc/cron.d/certbot-netbox`: + +``` +0 3 * * * root /usr/local/bin/certbot-netbox-renew >> /var/log/certbot-netbox-renew.log 2>&1 +``` + +`certbot renew` reads the renewal configuration written by step 7, so no +authenticator flags are needed here. Add a `--deploy-hook` (e.g. restarting +the web server) if services need to pick up the renewed certificate. + ## Installation Install into the same Python environment as certbot: ``` -pip install ./certbot-dns-netbox +pip install certbot-dns-netbox ``` ## Usage @@ -107,6 +218,6 @@ certbot certonly --dry-run \ -d sub.example.com # end-to-end against the staging ACME API ``` -Watch the `_acme-challenge` TXT records in the `global` view: their values +Watch the `_acme-challenge` TXT records in the `_default_` view: their values change to the challenge tokens during the run and remain in place afterwards. The next renewal updates the same records again. diff --git a/certbot_dns_netbox/dns_netbox.py b/certbot_dns_netbox/dns_netbox.py index ad227a1..0f68400 100644 --- a/certbot_dns_netbox/dns_netbox.py +++ b/certbot_dns_netbox/dns_netbox.py @@ -26,7 +26,7 @@ from certbot.plugins import dns_common logger = logging.getLogger(__name__) # netbox-dns view that contains the zone with the challenge TXT record. -search_view = "global" +search_view = "_default_" class Authenticator(dns_common.DNSAuthenticator):