v0.35.1: performance, AGPL licensing and documentation overhaul
- Web Server reads static files inline; proxy middleware chain flattened - About window with license summary and project links; AGPL-3.0-or-later - Rewritten documentation: feature list, screenshots, performance guide - Fix false spikes in per-Vhost request-rate charts
This commit is contained in:
1 parent
d3b492874b
commit
eba9f89283
135 files changed
+1425
-1064
No files matched your search
@@ -39,8 +39,7 @@ PROVIDER_FIELDS = {
|
||||
|
||||
|
||||
def credential(value: object) -> str:
|
||||
# All supported API keys are ASCII tokens. Reject whitespace/newlines and
|
||||
# INI metacharacters; provider input must never add options or sections.
|
||||
# API keys are ASCII tokens; reject anything that could inject INI content.
|
||||
if not isinstance(value, str) or not re.fullmatch(r"[A-Za-z0-9_./+\-=]{8,4096}", value):
|
||||
raise AcmeError("acme.invalid_api_credential")
|
||||
return value
|
||||
@@ -196,8 +195,7 @@ def certbot_command(value: dict, work: Path, credential_file: Path, command_pref
|
||||
|
||||
|
||||
def read_generated(work: Path, name: str) -> bytes:
|
||||
# Certbot live entries are symlinks into its private archive. Resolve only
|
||||
# within that archive, then open without following a second/final symlink.
|
||||
# Resolve live symlinks only inside Certbot's archive.
|
||||
archive = (work / "config" / "archive").resolve(strict=True)
|
||||
path = (work / "config" / "live" / "rustproxy" / name).resolve(strict=True)
|
||||
if not path.is_relative_to(archive):
|
||||
@@ -220,8 +218,7 @@ def issue(raw: dict, certificate_directory: Path, executable: str | None = None)
|
||||
private_directory(certificate_directory)
|
||||
root = certificate_directory / "acme"
|
||||
private_directory(root)
|
||||
# Each identity/environment has isolated CA accounts, DNS credentials and
|
||||
# Certbot lineages. Both profile IDs and directory paths are operator-owned.
|
||||
# Each profile has isolated CA accounts, credentials and lineages.
|
||||
suffix = "" if value["provider"] == "rfc2136" else "-" + value["provider"]
|
||||
work = root / (value["environment"] + "-" + value["certificate_id"] + suffix)
|
||||
private_directory(work)
|
||||
@@ -236,9 +233,7 @@ def issue(raw: dict, certificate_directory: Path, executable: str | None = None)
|
||||
raise AcmeError("acme.profile_busy") from None
|
||||
for folder in ["config", "work", "logs"]:
|
||||
private_directory(work / folder)
|
||||
# DNS credentials are supplied by RustProxy's encrypted Secret Store.
|
||||
# Certbot needs a file path, but the plaintext file exists only while the
|
||||
# provider command runs and is deleted before this worker returns.
|
||||
# Secret Store credentials exist as a plaintext file only while Certbot runs.
|
||||
credential_file = work / (value["provider"] + ".ini")
|
||||
if value["provider"] == "http01":
|
||||
webroot = certificate_directory / "acme-http01"
|
||||
@@ -265,8 +260,7 @@ def issue(raw: dict, certificate_directory: Path, executable: str | None = None)
|
||||
credential_file.unlink(missing_ok=True)
|
||||
cert = read_generated(work, "fullchain.pem")
|
||||
key = read_generated(work, "privkey.pem")
|
||||
# Immutable copies allow validation/publication without changing the
|
||||
# active certificate during CA work or a concurrent configuration edit.
|
||||
# Work on immutable copies so the active certificate never changes mid-run.
|
||||
stem = "acme-" + uuid.uuid4().hex
|
||||
cert_path = certificate_directory / (stem + ".pem")
|
||||
key_path = certificate_directory / (stem + ".key")
|
||||
@@ -392,8 +386,7 @@ def renew(raw: dict, certificate_directory: Path, executable: str | None = None)
|
||||
if not isinstance(names, list) or not 1 <= len(names) <= 16:
|
||||
raise AcmeError("acme.invalid_domains")
|
||||
names = [domain(n, wildcard=value["provider"] != "http01") for n in names]
|
||||
# The renewal config persists, but provider credentials do not. Recreate
|
||||
# the exact path referenced by Certbot only for this invocation.
|
||||
# Credentials are recreated only for the duration of this invocation.
|
||||
read_private(work / "config" / "renewal" / "rustproxy.conf", 65536)
|
||||
credential_file = work / (value["provider"] + ".ini")
|
||||
if value["provider"] != "http01":
|
||||
@@ -436,8 +429,7 @@ def renew(raw: dict, certificate_directory: Path, executable: str | None = None)
|
||||
return output
|
||||
if dates["not_after_unix"] < old_dates["not_after_unix"]:
|
||||
raise AcmeError("acme.renewal_older_certificate")
|
||||
# Content-addressed immutable staging makes a retry after a publication
|
||||
# failure idempotent (no additional orphan PEMs each time).
|
||||
# Content-addressed staging keeps retries idempotent.
|
||||
stem = "acme-" + hashlib.sha256(cert + b"\0" + key).hexdigest()[:48]
|
||||
paths = [(certificate_directory / (stem + ".pem"), cert), (certificate_directory / (stem + ".key"), key)]
|
||||
if len(list(certificate_directory.glob("acme-*.key"))) >= 1024 and not paths[1][0].exists():
|
||||
|
||||
Reference in new issue
Block a user