Files
RustProxy/docs/ACME_RFC2136.md
T
2026-10-03 00:27:09 +01:00

7.8 KiB

Basic Let's Encrypt / RFC2136 integration (0.5.0)

For the multi-provider UI, HTTP-01, Cloudflare and OVH, see ACME_PROVIDERS.md. Managed automatic renewal added in 0.7 is documented in RENEWAL.md. The RFC2136 fields and private account paths below remain supported.

Scope and prerequisites

This is an integrated control-plane adapter for Certbot + certbot-dns-rfc2136, not a newly written ACME or DNS-update protocol client. The plugin creates and cleans up DNS-01 TXT records through TSIG-signed RFC2136 updates. It must be installed separately. No real CA issuance or authoritative-DNS update has been performed in the packaging environment. Rust compilation, process integration and actual certificate deployment remain acceptance work (TEST_REPORT.md).

Use Linux/Unix with Python 3.10+, a current compatible Certbot/plugin installation, and python3 and certbot available in the RustProxy service's PATH. Check:

bash dev.sh acme --doctor
certbot --version
certbot plugins --authenticators

The doctor command reports executable discovery and the required plugin's name; it does NOT verify the plugin, DNS permissions or network connectivity. Nothing is installed automatically, and RustProxy never invokes sudo. A service hardening policy must allow child processes, the cert directory, DNS updates/queries to the chosen server and outbound HTTPS to the CA. Do not run the proxy as root just to make Certbot available. Use a private, service-owned cert directory (0700).

The operator must control publicly valid DNS names and an authoritative DNS server that permits the configured TSIG identity to change only the necessary _acme-challenge TXT names. Do not grant zone-wide update authority merely for certificates. A private update-server IP is acceptable when the resulting TXT records are visible to the public validation resolvers. Split DNS/views, CAA, replication delays and delegation must be checked on the operator's DNS setup. DNS-01 does not require opening the application's HTTP listener for a challenge.

Panel workflow

Open Certificates > Let's Encrypt / RFC2136. Enter a certificate library ID, contact email, comma-separated DNS names (including optional *.example.org), DNS update server IP/port, TSIG name, base64 secret, algorithm and propagation wait. ASCII/punycode DNS names only; up to 16 names. SHA256 is the default TSIG algorithm; SHA384/SHA512 are also allowed. SHA1/MD5 are not exposed. A TSIG key name may be a single label such as keyname.. Optional signed SOA queries support DNS servers/views that require them. Port defaults to 53; propagation defaults to 60 seconds (allowed 10..900).

Start with staging. Its certificates are not publicly trusted. Production requires a separate explicit confirmation, in addition to agreement with the Let's Encrypt subscriber agreement. The CA directory is fixed for each mode; arbitrary directory URLs, shell hooks and arbitrary extra Certbot options are not accepted by this interface.

Requesting starts one bounded asynchronous job, not a proxy-path operation. Refresh shows running, failed, or ready. At ready, the Rust control plane has checked PEM parsing, matching key and every requested DNS name before exposing only metadata. Click Add issued certificate to library, then select its ID in the vhost SSL tab and save. Replacing an existing ID requires confirmation and retains its legacy SNI host assignments; the whole current configuration is revalidated with revision CAS. Failed issuance/validation/import leaves active configuration alone. Certificates are loaded for new handshakes; changing listener protocols still requires restart. Existing connections retain their negotiated certificate.

Renewal and operator responsibilities

Configured RFC2136 managed profiles participate in the 0.7 renewal scheduler. Validated changed certificate material is published automatically; there is a per-certificate opt-out, explicit Check now and Dry run. Initial issuance/import remains explicit. The old 0.5 helper alone was issuance-only; do not confuse retained files or an unrelated OS Certbot timer with RustProxy publication. See RENEWAL.md for prerequisites, timing, rollback boundaries and legacy profiles. No external expiry notification, revocation UI or account manager is included.

Account state and TSIG credentials are kept privately under:

data/certs/acme/<staging-or-production>-<certificate-id>/
  rfc2136.ini       # SECRET, mode 0600
  profile.json     # non-secret parameters, mode 0600
  config/          # Certbot account, renewal, live and archive data
  work/
  logs/            # private Certbot diagnostics

The location follows the configured certificate directory, not always data/certs. There is one job per RustProxy process and a filesystem lock per profile. Bounds: 16 KiB request, 128 profiles, 128 staged ACME pairs and 128 KiB per PEM file. The provider timeout is 1200 seconds plus propagation wait; the Rust worker has a 2160-second outer deadline and stops its process group on cancellation/shutdown. Normal TXT cleanup belongs to the plugin. A hard kill, machine crash or DNS error can leave challenge TXT records: inspect DNS and clean only the failed challenge values, not unrelated records. Only the latest job status lives in memory; restart loses that status, not the private Certbot files.

TSIG is supplied through stdin to the helper, never argv, SQLite, exports or job responses. The browser clears the secret field after submission and does not put it in local/session storage. Private credentials/account files are deliberately retained for renewal; protect them and private logs as secrets. Keep unused staged pairs under operator control; active files are never silently deleted.

The existing dev.sh backup includes SQLite and configured PEM files, not the ACME account/profile directory. Back up certs/acme separately while no issuance is active, preserving ownership/modes and encryption appropriate to private keys. Source upgrade leaves the data directory alone. Do not ship profiles in source ZIPs.

API and local helper

GET /api/acme returns the latest sanitized job, provider name and automatic_renewal: false. POST /api/acme/issue requires the normal authenticated session, Origin/CSRF protections and an explicit bounded JSON request:

{
  "certificate_id": "example",
  "email": "operator@example.org",
  "domains": ["example.org", "*.example.org"],
  "environment": "staging",
  "agree_tos": true,
  "confirm_production": false,
  "dns_server": "192.0.2.53",
  "dns_port": 53,
  "key_name": "rustproxy.example.org.",
  "key_secret": "REPLACE_WITH_YOUR_PRIVATE_BASE64_TSIG_SECRET",
  "key_algorithm": "HMAC-SHA256",
  "sign_query": false,
  "propagation_seconds": 60
}

This documentation example is intentionally not an executable credential. A successful job submission returns 202; busy returns 429. Invalid input, consent and provider failures use fixed acme.* diagnostics rather than returning provider stderr or credentials. Look in the private profile logs for DNS/plugin errors. A 202 response means accepted, not issued.

Advanced local staging without the running API:

# request.json must be private (0600), with real operator-owned parameters.
bash dev.sh acme --cert-dir /absolute/private/cert-directory < request.json

This CLI only stages files and prints metadata. Unlike the integrated API, it cannot run Rust's key/SAN validation on its own and never activates a certificate. Import through the normal validated library/config workflow. Do not post secret JSON into shell arguments, source examples, tickets or logs.

Primary references