mirror of
https://github.com/Control-D-Inc/ctrld.git
synced 2026-09-04 13:36:35 +02:00
all: permit ctrld's own endpoints in Firewall Mode
Firewall Mode permits only what ctrld resolved through its own listener. The API transport resolves api.controld.com through the OS nameservers and falls back to hardcoded addresses, so nothing ever teaches the allowlist about it and ctrld's own block-all filters deny its control-plane socket. The upgrade download server has the same shape: performUpgrade spawns a detached child process, and the WFP filters carry no process condition, so the service blocks its own upgrade. Permit both permanently, at startup and on reload. For the API that means the resolved addresses and the transport's direct fallbacks - the fallbacks are what it dials when DNS is unusable, which is the state a blocked ctrld is in. For the download server only the fallback IP is needed, since its hostname lookup does go through the listener and is learned. APIDomain/APIEndpointIPs are exported so the permitted set and the dialed set cannot drift apart. Call initPlatformFirewall on reload even when enforcement is already up. AddPermanent fires no change callback, so an address permitted by a reload reached memory only while the platform never heard about it. Each platform's re-entry is a refresh: Windows reinstalls the permanent filters it is missing, macOS returns early. Also keep every dial attempt in the transport's error. It returned only the last stage, an unroutable IPv6 address reporting "no route to host", hiding the IPv4 WSAEACCES that named the real cause. The direct IPs are still always dialed, so the API stays reachable without DNS; only a duplicate dial of an address the resolver already returned is dropped.
This commit is contained in:
@@ -87,14 +87,18 @@ func (p *prog) shutdownPlatformFirewall() {
|
||||
// initPlatformFirewall initializes Windows-specific firewall enforcement (WFP filters).
|
||||
func (p *prog) initPlatformFirewall() {
|
||||
if fwState, ok := p.platformFirewallState.(*wfpFirewallState); ok && fwState != nil {
|
||||
// A reload re-enters here with enforcement already up, to install permits
|
||||
// for permanent entries added since - a newly resolved API address, say -
|
||||
// which AddPermanent records in memory without any callback that would
|
||||
// reach WFP. Both populate calls skip what they already hold, so this is a
|
||||
// refresh rather than a reinstall.
|
||||
fwState.populatePermanentFilters(p)
|
||||
// Both callers gate on platformFirewallState being nil, so nothing reaches
|
||||
// this today. Should something re-initialize enforcement over existing
|
||||
// state, the filter IDs this state holds describe whatever engine session
|
||||
// installed them, which is not necessarily the live one - so ask for a full
|
||||
// replace instead of a delta against a snapshot that may describe filters
|
||||
// that no longer exist. markDestinationsForResync is idempotent and cheap,
|
||||
// and an unnecessary replace is a no-op the mirrors already tolerate.
|
||||
// The filter IDs this state holds describe whatever engine session
|
||||
// installed them, which after a rebuildDNSIntercept is not the live one -
|
||||
// so ask for a full replace instead of a delta against a snapshot that may
|
||||
// describe filters that no longer exist. markDestinationsForResync is
|
||||
// idempotent and cheap, and an unnecessary replace is a no-op the mirrors
|
||||
// already tolerate.
|
||||
p.markDestinationsForResync()
|
||||
p.reconcileAllowedDestinations()
|
||||
fwState.populateFilters(p)
|
||||
|
||||
Reference in New Issue
Block a user