v0.24.9
This commit is contained in:
1 parent
1297ea2920
commit
dc0fcabf51
49 files changed
+2632
-252
No files matched your search
+60
-36
@@ -28,7 +28,7 @@ ALGORITHMS = {"HMAC-SHA256", "HMAC-SHA384", "HMAC-SHA512"}
|
||||
MAX_INPUT = 16384
|
||||
MAX_PEM = 131072
|
||||
PROVIDERS = {"http01", "cloudflare", "ovh", "rfc2136"}
|
||||
SECRET_FIELDS = {"key_secret", "api_token", "application_key", "application_secret", "consumer_key"}
|
||||
SECRET_FIELDS = {"key_secret", "api_token", "application_key", "application_secret", "consumer_key", "credential_ini_b64"}
|
||||
COMMON_FIELDS = {"provider", "certificate_id", "email", "domains", "environment", "agree_tos", "confirm_production", "propagation_seconds"}
|
||||
PROVIDER_FIELDS = {
|
||||
"http01": set(),
|
||||
@@ -226,29 +226,33 @@ 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)
|
||||
# Keep credentials for this lineage's renewal. Replace atomically; never
|
||||
# follow an existing credential-file symlink or put secrets in SQLite.
|
||||
# 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.
|
||||
credential_file = work / (value["provider"] + ".ini")
|
||||
if value["provider"] == "http01":
|
||||
webroot = certificate_directory / "acme-http01"
|
||||
for path in (webroot, webroot / ".well-known", webroot / ".well-known" / "acme-challenge"):
|
||||
private_directory(path)
|
||||
else:
|
||||
temporary = work / ("credentials-" + uuid.uuid4().hex)
|
||||
write_new(temporary, credentials(value))
|
||||
os.replace(temporary, credential_file)
|
||||
profile = {k: v for k, v in value.items() if k not in SECRET_FIELDS}
|
||||
temporary = work / ("profile-" + uuid.uuid4().hex)
|
||||
write_new(temporary, json.dumps(profile).encode())
|
||||
os.replace(temporary, work / "profile.json")
|
||||
args = certbot_command(value, work, credential_file, executable)
|
||||
credential_file.unlink(missing_ok=True)
|
||||
write_new(credential_file, credentials(value))
|
||||
try:
|
||||
result = subprocess.run(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=1200 + value["propagation_seconds"], check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise AcmeError("acme.provider_timeout") from None
|
||||
if result.returncode:
|
||||
raise AcmeError("acme.certbot_failed_check_private_log")
|
||||
profile = {k: v for k, v in value.items() if k not in SECRET_FIELDS}
|
||||
temporary = work / ("profile-" + uuid.uuid4().hex)
|
||||
write_new(temporary, json.dumps(profile).encode())
|
||||
os.replace(temporary, work / "profile.json")
|
||||
args = certbot_command(value, work, credential_file, executable)
|
||||
try:
|
||||
result = subprocess.run(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=1200 + value["propagation_seconds"], check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise AcmeError("acme.provider_timeout") from None
|
||||
if result.returncode:
|
||||
raise AcmeError("acme.certbot_failed_check_private_log")
|
||||
finally:
|
||||
if value["provider"] != "http01":
|
||||
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
|
||||
@@ -313,7 +317,7 @@ def certificate_dates(pem: bytes, executable: str | None = None) -> dict:
|
||||
|
||||
|
||||
def renewal_request(raw: object) -> dict:
|
||||
if not isinstance(raw, dict) or set(raw) - {"action", "certificate_id", "provider", "environment", "cert_file", "key_file", "dry_run"}:
|
||||
if not isinstance(raw, dict) or set(raw) - {"action", "certificate_id", "provider", "environment", "cert_file", "key_file", "dry_run", "credential_ini_b64"}:
|
||||
raise AcmeError("acme.invalid_renewal_request")
|
||||
if raw.get("action") != "renew" or raw.get("provider") not in PROVIDERS or raw.get("environment") not in DIRECTORIES:
|
||||
raise AcmeError("acme.invalid_renewal_request")
|
||||
@@ -325,7 +329,20 @@ def renewal_request(raw: object) -> dict:
|
||||
raise AcmeError("acme.invalid_renewal_request")
|
||||
if type(raw.get("dry_run", False)) is not bool:
|
||||
raise AcmeError("acme.invalid_renewal_request")
|
||||
return dict(raw, dry_run=raw.get("dry_run", False))
|
||||
if raw["provider"] != "http01":
|
||||
encoded = raw.get("credential_ini_b64")
|
||||
try:
|
||||
credential_ini = base64.b64decode(encoded, validate=True) if isinstance(encoded, str) else b""
|
||||
except (ValueError, TypeError):
|
||||
credential_ini = b""
|
||||
if not credential_ini or len(credential_ini) > 65536 or b"\x00" in credential_ini:
|
||||
raise AcmeError("acme.invalid_api_credential")
|
||||
else:
|
||||
credential_ini = b""
|
||||
value = dict(raw, dry_run=raw.get("dry_run", False))
|
||||
value["credential_ini"] = credential_ini
|
||||
value.pop("credential_ini_b64", None)
|
||||
return value
|
||||
|
||||
|
||||
def renew(raw: dict, certificate_directory: Path, executable: str | None = None) -> dict:
|
||||
@@ -367,30 +384,37 @@ 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]
|
||||
# This exact renewal file must have been created by the original issuance.
|
||||
# The renewal config persists, but provider credentials do not. Recreate
|
||||
# the exact path referenced by Certbot only for this invocation.
|
||||
read_private(work / "config" / "renewal" / "rustproxy.conf", 65536)
|
||||
credential_file = work / (value["provider"] + ".ini")
|
||||
if value["provider"] != "http01":
|
||||
read_private(work / (value["provider"] + ".ini"))
|
||||
credential_file.unlink(missing_ok=True)
|
||||
write_new(credential_file, value["credential_ini"])
|
||||
else:
|
||||
for path in [certificate_directory / "acme-http01", certificate_directory / "acme-http01" / ".well-known",
|
||||
certificate_directory / "acme-http01" / ".well-known" / "acme-challenge"]:
|
||||
private_directory(path)
|
||||
installed_cert = read_private(certificate_directory / value["cert_file"], MAX_PEM)
|
||||
installed_key = read_private(certificate_directory / value["key_file"], MAX_PEM)
|
||||
# Check dependencies/installed PEM before invoking a CA operation.
|
||||
old_dates = certificate_dates(installed_cert)
|
||||
args = [executable, "renew", "--cert-name", "rustproxy", "--non-interactive",
|
||||
"--no-random-sleep-on-renew", "--no-directory-hooks",
|
||||
"--config-dir", str(work / "config"), "--work-dir", str(work / "work"), "--logs-dir", str(work / "logs")]
|
||||
if value["dry_run"]:
|
||||
args.append("--dry-run")
|
||||
try:
|
||||
result = subprocess.run(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=2100, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise AcmeError("acme.provider_timeout") from None
|
||||
if result.returncode:
|
||||
raise AcmeError("acme.certbot_failed_check_private_log")
|
||||
installed_cert = read_private(certificate_directory / value["cert_file"], MAX_PEM)
|
||||
installed_key = read_private(certificate_directory / value["key_file"], MAX_PEM)
|
||||
# Check dependencies/installed PEM before invoking a CA operation.
|
||||
old_dates = certificate_dates(installed_cert)
|
||||
args = [executable, "renew", "--cert-name", "rustproxy", "--non-interactive",
|
||||
"--no-random-sleep-on-renew", "--no-directory-hooks",
|
||||
"--config-dir", str(work / "config"), "--work-dir", str(work / "work"), "--logs-dir", str(work / "logs")]
|
||||
if value["dry_run"]:
|
||||
args.append("--dry-run")
|
||||
try:
|
||||
result = subprocess.run(args, stdin=subprocess.DEVNULL, stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL, timeout=2100, check=False)
|
||||
except subprocess.TimeoutExpired:
|
||||
raise AcmeError("acme.provider_timeout") from None
|
||||
if result.returncode:
|
||||
raise AcmeError("acme.certbot_failed_check_private_log")
|
||||
finally:
|
||||
if value["provider"] != "http01":
|
||||
credential_file.unlink(missing_ok=True)
|
||||
output = {k: value[k] for k in ("provider", "certificate_id", "environment")}
|
||||
output.update(domains=names, changed=False, staged=False, dry_run=value["dry_run"], **old_dates)
|
||||
if value["dry_run"]:
|
||||
|
||||
Reference in new issue
Block a user