mirror of
https://github.com/gesellix/Bose-SoundTouch.git
synced 2026-08-18 08:36:13 +00:00
fix(health): don't error when the advertised HTTPS URL is unreachable from the service
The `service_cert_chain` check ("HTTPS endpoint TLS configuration") dials
the service's own configured HTTPS URL. When that dial fails before any
certificate is presented (connection refused, timeout, handshake reset),
it reported a hard red error.
But from inside the service we can't distinguish "the endpoint is down"
from "the advertised HTTPS URL simply isn't reachable from here" — and
the latter is a normal, healthy deployment: TLS terminated by a reverse
proxy in front of AfterTouch, or a Docker-published port / LAN-only
hostname that the container itself can't dial. In those setups the red
error is a false alarm (issue #355: reporter runs HTTP 8080 / HTTPS 8443
and noted "in my configuration that is expected").
Downgrade that specific case (no cert presented) to a warning, reword it
to name the expected reverse-proxy / unreachable-advertised-URL case, and
add an `openssl s_client` command to verify the endpoint from a client
that actually reaches the advertised URL. Cert-classification outcomes
(own-CA info, foreign-chain warning) are unchanged.
Reproduced locally on a clean data dir before/after: custom ports and
localhost/127.0.0.1 already returned INFO; only the unreachable-URL case
produced the error, which now returns a warning.
refs #355
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
c3e3391db6
commit
9d69b61779
@@ -29,6 +29,10 @@ const CheckIDCertChain = "service_cert_chain"
|
||||
// something else → warning with an `openssl s_client`
|
||||
// investigation prompt (foreign chain / reverse proxy /
|
||||
// ingress cert).
|
||||
// - endpoint not reachable from inside the service (no cert
|
||||
// presented) → warning, not error: the advertised HTTPS URL
|
||||
// is often intentionally unreachable from the service itself
|
||||
// (reverse proxy, Docker-published port, LAN-only hostname).
|
||||
// - HTTPS URL not configured → skip silently.
|
||||
//
|
||||
// caCertFn returns AfterTouch's own CA leaf certificate (nil if
|
||||
@@ -82,10 +86,27 @@ func runCertChainCheck(httpsURL string, caCertFn func() *x509.Certificate) []Fin
|
||||
// reachability problem.
|
||||
leaf := leafFromVerifyError(err)
|
||||
if leaf == nil {
|
||||
// The dial failed before any certificate was presented
|
||||
// (connection refused, timeout, handshake reset). From
|
||||
// inside the service we can't tell "the endpoint is down"
|
||||
// apart from "the advertised HTTPS URL simply isn't
|
||||
// reachable from here" — the latter is a normal, healthy
|
||||
// setup (TLS terminated by a reverse proxy, or a
|
||||
// Docker-published port / external hostname that only
|
||||
// resolves on the LAN). Reporting a hard error there is a
|
||||
// false alarm (issue #355), so this is a warning with the
|
||||
// context to tell the two apart.
|
||||
return []Finding{{
|
||||
Severity: SeverityError,
|
||||
Message: fmt.Sprintf("Could not connect to %s: %v", addr, err),
|
||||
Details: "AfterTouch's HTTPS endpoint isn't reachable from inside the service, or the peer dropped the handshake before presenting a certificate. Check that the listener is bound and the URL host:port resolves correctly.",
|
||||
Severity: SeverityWarning,
|
||||
Message: fmt.Sprintf("Configured HTTPS endpoint %s isn't reachable from inside the service.", addr),
|
||||
Details: fmt.Sprintf("AfterTouch dialed its own configured HTTPS URL (%s) and the connection failed before any certificate was presented: %v. "+
|
||||
"This is expected — and not a problem — if TLS is terminated by a reverse proxy in front of AfterTouch, or the advertised HTTPS URL isn't reachable from inside the container (for example a Docker-published port, or a hostname that only resolves elsewhere on your network). In that case, verify the endpoint from a client instead (see below). "+
|
||||
"If AfterTouch is meant to serve HTTPS directly, check that the HTTPS listener is bound and that the URL host:port is correct.", httpsURL, err),
|
||||
ManualCommands: []ManualCommand{{
|
||||
Label: "Verify the endpoint from a machine on your network:",
|
||||
Command: fmt.Sprintf("openssl s_client -connect %s -servername %s </dev/null", addr, host),
|
||||
Hint: "Run from a client that reaches the advertised URL (not necessarily the service host). A successful handshake there means the endpoint is fine and this warning is expected for your setup.",
|
||||
}},
|
||||
}}
|
||||
}
|
||||
|
||||
|
||||
@@ -29,11 +29,27 @@ func TestCertChain_UnparseableURLWarns(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertChain_UnreachableEndpoint(t *testing.T) {
|
||||
// 127.0.0.1:1 refuses; using https:// to force TLS path.
|
||||
func TestCertChain_UnreachableEndpoint_IsWarningNotError(t *testing.T) {
|
||||
// Regression for issue #355: the service dialing its own
|
||||
// configured HTTPS URL and finding it unreachable is NOT a hard
|
||||
// error. The advertised URL is frequently unreachable from
|
||||
// inside the service on purpose (TLS terminated by a reverse
|
||||
// proxy, a Docker-published port, or a LAN-only hostname), so a
|
||||
// red error there is a false alarm. It must be a warning that
|
||||
// explains the expected case and offers a client-side check.
|
||||
//
|
||||
// 127.0.0.1:1 refuses; using https:// to force the TLS path.
|
||||
got := runCertChainCheck("https://127.0.0.1:1/", nil)
|
||||
if len(got) != 1 || got[0].Severity != SeverityError {
|
||||
t.Fatalf("expected one error for unreachable endpoint, got %+v", got)
|
||||
if len(got) != 1 || got[0].Severity != SeverityWarning {
|
||||
t.Fatalf("expected one warning for unreachable endpoint, got %+v", got)
|
||||
}
|
||||
|
||||
if !strings.Contains(got[0].Details, "reverse proxy") {
|
||||
t.Errorf("expected details to name the reverse-proxy / unreachable-advertised-URL case, got %q", got[0].Details)
|
||||
}
|
||||
|
||||
if len(got[0].ManualCommands) == 0 || !strings.Contains(got[0].ManualCommands[0].Command, "openssl s_client") {
|
||||
t.Errorf("expected an openssl s_client client-side verification command, got %+v", got[0].ManualCommands)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user