-
Notifications
You must be signed in to change notification settings - Fork 0
Batching to avoid acme recursive domain safeguard #8
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,4 +20,5 @@ src/certapi.egg-info | |
| !/.vscode/launch.json | ||
| .venv | ||
| .env | ||
| test_db_temp/ | ||
| test_db_temp/ | ||
| *.key | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,82 @@ | ||||||||||||||||||||||||||||||||
| def _normalize_domain(domain: str) -> str: | ||||||||||||||||||||||||||||||||
| domain = domain.strip().lower().rstrip(".") | ||||||||||||||||||||||||||||||||
| if domain.startswith("*."): | ||||||||||||||||||||||||||||||||
| domain = domain[2:] | ||||||||||||||||||||||||||||||||
| return domain | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def _split_labels(domain: str) -> list[str]: | ||||||||||||||||||||||||||||||||
| normalized = _normalize_domain(domain) | ||||||||||||||||||||||||||||||||
| return [label for label in normalized.split(".") if label] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def would_trigger(labels: list[str], blocked: set[str]) -> bool: | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Boulder-style recursive on-demand trigger check used by tests. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| if len(labels) < 4: | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| blocked = {label.lower() for label in blocked} | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| consecutive_blocked = 0 | ||||||||||||||||||||||||||||||||
| prev_label: str | None = None | ||||||||||||||||||||||||||||||||
| for label in labels: | ||||||||||||||||||||||||||||||||
| label = label.lower() | ||||||||||||||||||||||||||||||||
| is_blocked = label in blocked | ||||||||||||||||||||||||||||||||
| if is_blocked: | ||||||||||||||||||||||||||||||||
| consecutive_blocked += 1 | ||||||||||||||||||||||||||||||||
| if prev_label is not None and prev_label == label: | ||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
| if consecutive_blocked >= 3: | ||||||||||||||||||||||||||||||||
| return True | ||||||||||||||||||||||||||||||||
| else: | ||||||||||||||||||||||||||||||||
| consecutive_blocked = 0 | ||||||||||||||||||||||||||||||||
| prev_label = label | ||||||||||||||||||||||||||||||||
| return False | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def split_domain_to_safe_groups(domain: str, blocked_labels: list[str] | None = None) -> list[list[str]]: | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Split labels into contiguous chunks of at most 3 labels. | ||||||||||||||||||||||||||||||||
| This mirrors the "3 labels are always safe for this specific LE heuristic" | ||||||||||||||||||||||||||||||||
| invariant, but these label groups are not used directly as issuance domains. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| labels = _split_labels(domain) | ||||||||||||||||||||||||||||||||
| n = len(labels) | ||||||||||||||||||||||||||||||||
| if n == 0: | ||||||||||||||||||||||||||||||||
| return [] | ||||||||||||||||||||||||||||||||
| return [labels[i : i + 3] for i in range(0, n, 3)] | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| def create_safe_domain_batches(domains: list[str], blocked_labels: list[str] | None = None) -> list[list[str]]: | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| Create compact issuance batches without any blocked-label list assumptions. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| Rules: | ||||||||||||||||||||||||||||||||
| - Domains with <= 3 labels are grouped together into one compact batch. | ||||||||||||||||||||||||||||||||
| - Longer domains are split into safe label groups and emitted as singleton batches. | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| This avoids guessed label lists and keeps behavior deterministic. | ||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||
| compact_batch: list[str] = [] | ||||||||||||||||||||||||||||||||
| singleton_batches: list[list[str]] = [] | ||||||||||||||||||||||||||||||||
| seen: set[str] = set() | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for domain in domains: | ||||||||||||||||||||||||||||||||
| normalized = _normalize_domain(domain) | ||||||||||||||||||||||||||||||||
| if not normalized or normalized in seen: | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
| seen.add(normalized) | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| labels = [label for label in normalized.split(".") if label] | ||||||||||||||||||||||||||||||||
| if len(labels) <= 3: | ||||||||||||||||||||||||||||||||
| compact_batch.append(normalized) | ||||||||||||||||||||||||||||||||
| continue | ||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| for i in range(0, len(labels), 3): | ||||||||||||||||||||||||||||||||
| singleton_batches.append([".".join(labels[i : i + 3])]) | ||||||||||||||||||||||||||||||||
|
Comment on lines
+72
to
+78
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The logic for handling domains with more than 3 labels is fundamentally incorrect for ACME certificate issuance.
If the goal is to isolate potentially 'unsafe' domains to avoid triggering the safeguard for the entire order, you should group the full domain strings into separate batches (singleton batches) rather than splitting the labels of a single domain.
Suggested change
|
||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||
| if compact_batch: | ||||||||||||||||||||||||||||||||
| return [compact_batch, *singleton_batches] | ||||||||||||||||||||||||||||||||
| return singleton_batches | ||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The CLI
obtaincommand is missing a flag to enable the new batching functionality. While the API andCertManagerClientwere updated to supportbatch_domains, the CLI still performs a single issuance for all domains because it callsAcmeCertIssuerdirectly instead of using theAcmeCertManagerwrapper. To ensure consistency and make the feature accessible to CLI users, consider adding a--batchargument and updating the implementation to useAcmeCertManager.issue_certificate_in_batches.