Skip to content

Delegation checker and automatic limit_domains management - #1235

Open
peterthomassen wants to merge 15 commits into
mainfrom
20260816_delegation_checks
Open

Delegation checker and automatic limit_domains management#1235
peterthomassen wants to merge 15 commits into
mainfrom
20260816_delegation_checks

Conversation

@peterthomassen

@peterthomassen peterthomassen commented Aug 16, 2026

Copy link
Copy Markdown
Member

Different approach than #1174

Testing using production database on local machine takes about 79 minutes with 4 threads, 47 minutes with 8 threads, 44 minutes with 16 threads, and 39 minute with 64 threads. 8 threads is probably best: the more threads, the more resolution errors (maybe rate limits at TLD auths or something).

@peterthomassen
peterthomassen force-pushed the 20260816_delegation_checks branch from adabf3d to 245e4e0 Compare August 17, 2026 01:50
@peterthomassen peterthomassen changed the title Delegation checker Delegation checker and automatic limit_domains management Aug 17, 2026
@peterthomassen

peterthomassen commented Aug 17, 2026

Copy link
Copy Markdown
Member Author

Extended PR with automatic domain limit management. Effective domain limit is number of domains securely delegated to us (s, includes .dedyn.io domains), plus the square root of external domains securely delegated to us (e, without .dedyn.io domains; but: at least 1). This way, the headroom for creating more domains without immediately securing them (DS record) grows sensibly:

limit = s + max(1, h)    with  h = round(√e)

For users without .dedyn.io domains, this is equivalent to (e + sqrt(e)); .dedyn.io domains are added on top (effectively not influencing the limit besides offsetting it).

Illustrations:

Account without dedyn.io domain

All secure domains s are external.

Secure domains s Headroom h = round(√s) Effective limit
0 0 1
1 1 2
2 1 3
3 2 5
4 2 6
5 2 7
7 3 10
10 3 13
12 3 15
15 4 19
20 4 24
25 5 30
30 5 35
40 6 46
50 7 57
75 9 84
100 10 110

Account with dedyn.io domain

Secure domains s include the .dedyn.io domain. The limit is effectively raised by 1 for the first few domains. From ~10 domains on, the situation is the same as above.

Secure domains s Headroom h = round(√(s−1)) Effective limit
1 0 2
2 1 3
3 1 4
4 2 6
5 2 7
7 2 9
10 3 13
12 3 15
15 4 19
20 4 24
25 5 30
30 5 35
40 6 46
50 7 57
75 9 84
100 10 110

@peterthomassen
peterthomassen marked this pull request as ready for review August 17, 2026 01:57
@peterthomassen
peterthomassen force-pushed the 20260816_delegation_checks branch 4 times, most recently from 7a783ac to 6560072 Compare August 18, 2026 00:50
DomainSerializer rejects a name below one of our local public suffixes that
is not an immediate child of it, such as sub.example.dedyn.io, with the
name_too_deep code. Signup reports that message as it stands instead of
replacing it with the generic name_unavailable one.
Creating a second domain under one of our own public suffixes is now
rejected; accounts that already hold several keep them. DomainQuerySet
gains under_local_public_suffix(), built on a new
under_local_public_suffix_q() that matches at any depth below one of our
own public suffixes.
Describes how a hosted zone is checked for being correctly delegated to
deSEC and correctly secured, and how the outcomes are recorded: what is
measured and from where, where the code lives, what is stored, and what
is deliberately deferred.
New compose service running a validating recursive resolver, built from
source for its flush_delegation command. It sits alone with api on the new
rearapi_unbound network, serving DNS on port 53 and remote control on 8953.
desecapi.unbound speaks unbound's remote control protocol: flush_delegation()
drops a name's delegation from the resolver's cache, and query() sends it a
recursive, DNSSEC-enabled query. Settings gain UNBOUND_HOST, UNBOUND_PORT and
UNBOUND_CONTROL_PORT.
New DelegationCheck model holding the outcome of one check: the two status
dimensions, the nameservers seen, and the rcode and EDE of the response that
settled the security status. DelegationCheck.objects.record() bumps the
confirmation time when an outcome agrees with the domain's current one, and
inserts a new row otherwise. Domain gains current_delegation_check, pointing
at the newest one.
desecapi.delegation.check() returns the nameservers a name is delegated to
and whether that delegation is secured, as a DelegationCheckResult that
touches no database. It flushes the delegation from the resolver's cache,
locates the parent zone, reads the delegation from the parent's nameservers,
and takes the security status from our resolver's AD bit. desecapi.dns gains
query_server() for the non-recursive queries to authoritative servers.
Checks the named domains, all of them (--all), or those not checked within a
given number of seconds (--stale), records the results, and prints one line
per domain. --include-local extends bulk selection to locally registrable
domains, which are otherwise skipped; --dry-run prints without recording;
--concurrency sizes the thread pool.
Describes how the domain limit becomes a function of the domains a user has
securely delegated to us, how checks are scheduled and triggered, which
decisions were taken along the way, and what is deferred.
A null User.limit_domains -- now the default -- makes the enforced limit
s + max(DOMAIN_LIMIT_INSECURE_HEADROOM, round(sqrt(e))), with s the user's
securely delegated domains and e the externally delegated ones; an explicit
value still pins it. LIMIT_USER_DOMAIN_COUNT_DEFAULT is renamed to
DOMAIN_LIMIT_INSECURE_HEADROOM, its environment variable unchanged, and now
floors the headroom rather than the limit. Domain.secure_delegation_since,
maintained by DelegationCheck.objects.record(), is what those counts read. The
account endpoint keeps reporting limit_domains as the enforced number and gains
secure_domains. Migration 0048 adds the field, backfills it from the checks
already recorded, and makes limit_domains nullable by default.
Support can pin an account's domain limit by setting a number; there was no
way back. "auto" writes null, which is what the computed limit is spelled as,
and the command reports the value that results rather than the null it wrote.
New celery-delegation service, a second instance of the api image consuming
the delegation_bulk and delegation_adhoc queues at one domain per task.
desecapi.tasks holds check_domain_delegation, which re-checks staleness before
measuring and leaves the domain for later when the resolver is unavailable,
and plan_user_delegation_checks, which puts a user's not-yet-secure domains on
the ad-hoc queue, capped at 20 per plan and one plan per user per minute.
check-delegation with --concurrency 0 enqueues one task per selected domain
on the bulk queue instead of checking them inline, reusing the same selection
and passing --stale on so the workers can re-check it; --dry-run reports what
would be enqueued. Negative values are rejected.
A cron entry in the api container runs check-delegation --stale 79200
--concurrency 0 every two hours, which gets each domain re-checked about once
a day.
Creating a domain, and being denied one by the domain limit, both enqueue
checks for the user's domains that are not yet known to be secure. Bounded to
one plan per user per minute and 20 domains per plan; neither trigger raises.
The limit message now says that a re-check is under way.
@peterthomassen
peterthomassen force-pushed the 20260816_delegation_checks branch from 6560072 to 889ad0e Compare August 18, 2026 01:16
@peterthomassen

Copy link
Copy Markdown
Member Author

I've performed a detailed/thorough review, and applied various improvements / rearranged commits. I did not find any problematic issues.

The first two commits initially might seem unrelated, but they are needed so that the later limit calculation logic works reliably.

Perhaps we should drop the commits containing the plans.

@nils-wisiol ready for final review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants