2.9 KiB
← Wiring it up · Module README · Composing filters →
6. Operating it
What the caller sees
FilteredHostException is a plain RuntimeException. Uncaught in a controller it is a bare
HTTP 500, and Boot's default error body does not name the host — so the first symptom in
production is a 500 with nothing useful in the response and a stack trace in the log.
Catch it. It carries the two things you want:
catch (FilteredHostException ex) {
log.warn("outbound call to {} blocked by {}", ex.getHost(), ex.getFilter());
return ResponseEntity.status(HttpStatus.BAD_GATEWAY).body(...);
}
getHost() is the host string from the URI, not the resolved address — Filtered host 'localhost', not Filtered host '127.0.0.1'. On the Apache path the resolved addresses were
available and were not kept, so if you want to log which address triggered it you have to
resolve again yourself.
A 4xx is arguably the better answer when the URL came from a user, since the request is what is
wrong. Do not echo ex.getMessage() back to them: it confirms which hosts are unreachable,
which turns your error response into a network scanner. Log it, return something bland.
Alert on it
A FilteredHostException is either an attack or an outage, and you want to know which. Both are
worth paging on eventually, but the second is the one that will bite you first: an allow-list
pinned to IP ranges fails the day the destination changes its DNS. The allowlist profile in
this module was written against 93.184.216.34, which was example.com's address for a decade
and is not any more.
Testing it
The filter is a @FunctionalInterface with no Spring dependencies, so the allow/deny decision
is a unit test — no context, no network:
assertThat(InetAddressFilter.externalAddresses()
.matches(InetAddress.getByName("169.254.169.254"))).isFalse();
FilterMatrix is that idea with a
table around it. Run it against your own filter before you deploy it; the four-row disagreement
in chapter 3 is not something you would find by reading.
The diagnostic endpoint
/diag/filter?host=... reports
whether a filter bean is present, whether the settings picked it up, and the verdict on every
address the host resolves to:
{ "filterBeanPresent": true, "settingsCarryFilter": true, "host": "example.com",
"resolvesTo": { "172.66.147.243": true, "104.20.23.154": true } }
That answers the question you actually have when an outbound call fails, which is not "what does my configuration say" but "what does the running context think". Delete it before shipping: it is an oracle for your outbound allow-list and a host-resolution service for anyone who finds it.