fix(domains): validate against all server IPs and add per-domain DNS validation mode#4659
fix(domains): validate against all server IPs and add per-domain DNS validation mode#4659pparage wants to merge 2 commits into
Conversation
… validation mode Domain validation compared a domain's resolved IPs against a single expected IP (the server's stored SSH ipAddress, or the Dokploy host's serverIp). Servers commonly have more than one address (an internal SSH IP plus a separate public IP that DNS points to), so a correctly configured domain was reported as Invalid. Layer 1: validation now gathers every IP the target server is reachable at (the stored address, globally scoped interface addresses, and the detected public egress IP) and passes when the domain resolves to any of them. IPs are collected over SSH for remote servers and locally for the Dokploy host, falling back to the stored address if the server cannot be reached. Layer 2: adds a per-domain DNS validation mode for cases where DNS does not point at the server itself: - auto: match against the server's detected IPs (default) - proxy: match against a user-provided IP (reverse proxy / load balancer) - skip: only confirm the domain resolves Refs Dokploy#4658
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 815f07d7e6
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| domain: input.domain, | ||
| validationMode: input.validationMode, | ||
| expectedIp: input.expectedIp, | ||
| serverId: input.serverId, |
There was a problem hiding this comment.
Authorize server IDs before remote IP collection
When a caller supplies serverId, this path passes it directly into validateDomainForServer, which calls getServerIps and then findServerById/execAsyncRemote; however this procedure only checks domain:read and does not verify that the server belongs to the caller's active organization or is in getAccessibleServerIds. In member/custom-role contexts that can read domains but not servers, a crafted validation request with a known server ID can make Dokploy SSH to that server and return collected IPs in mismatch errors, so the server ID should be derived from an already-authorized service/domain or checked before use.
Useful? React with 👍 / 👎.
| } | ||
|
|
||
| if (validationMode === "proxy") { | ||
| return validateDomain(domain, expectedIp ? [expectedIp] : []); |
There was a problem hiding this comment.
Reject proxy validation without an expected IP
In proxy mode, an omitted or empty expectedIp is converted to an empty expected-IP list, and validateDomain treats that as resolve-only success. Because the server-side create/update schemas and this validation mutation allow expectedIp to be null/omitted, API or stale clients can save/use validationMode: "proxy" without an IP and get a valid badge for any resolving domain—the same behavior as skip; this branch should fail or require a non-empty expected IP.
Useful? React with 👍 / 👎.
…ected IP in proxy mode Address review feedback on the domain validation endpoint: - The validateDomain procedure accepted a client-supplied serverId and used it to SSH to that server (via getServerIps) while only checking domain:read. A caller could target a server in another organization. It now verifies the serverId is in the caller's accessible servers before use, mirroring the guard used by the database routers. - In proxy mode an omitted or empty expectedIp fell through to a resolve-only "valid" result, silently behaving like skip. Proxy mode now rejects a missing or blank expected IP.
What is this PR about?
Domain validation (the "Validate" badge on a domain) compared a domain's resolved IPs against a single expected IP: the remote server's stored SSH
ipAddress, or the Dokploy host'sserverIp. Servers commonly have more than one address (an internal or SSH IP plus a separate public IP that DNS points to), so a correctly configured domain was reported as Invalid. This is the false negative described in #4658.This PR addresses it in two layers:
Layer 1, automatic detection (the bug fix). Validation now gathers every IP the target server is reachable at: the stored address, the globally scoped interface addresses, and the detected public egress IP. The domain is valid when it resolves to any of them. IPs are collected over SSH for remote servers and locally for the Dokploy host, and any failure to reach the server falls back to the stored address, so behavior never regresses below what it was before.
Layer 2, per domain DNS validation mode. For setups where DNS legitimately does not point at the server itself (a reverse proxy, a load balancer, a floating VIP, Cloudflare Tunnel), each domain gets a small validation mode control:
auto: match against the server's detected IPs (default, the Layer 1 behavior).proxy: match against a user provided IP (the proxy or load balancer address).skip: only confirm the domain resolves, without matching an IP.This generalizes the existing hardcoded CDN special case into an explicit, user driven setting.
Implementation notes
validateDomainnow takes the full set of expected IPs and passes when the resolved set intersects it.getServerIpshelper collects the server's addresses (interfaces plus egress), andvalidateDomainForServermaps the validation mode to the right expected IPs.validationMode(enum, defaultauto) andexpectedIpcolumns on thedomaintable, with a drizzle migration.Testing
__test__/domain/validate-domain.test.tscovering the multi IP case, a genuine mismatch, and the proxy and skip modes.pnpm --filter server typecheckandpnpm --filter dokploy typecheckpass.Checklist
canarybranch.Issues related (if applicable)
closes #4658