mirror of
https://github.com/netbirdio/netbird.git
synced 2026-09-26 08:39:06 +02:00
[management] Classify a host that will not resolve as unreachable
The SSRF guard resolves the host before any request is built, so a name that does not resolve fails there rather than at the transport, and that error reached the caller unclassified — an operator with a typo in the hostname was told the provider could not be checked rather than that the url could not be reached. It is the commonest way for an upstream to be wrong. The live suite is what caught it: the unit tests construct the transport errors directly and so never went through the guard. The management fixture moves to a private upstream in the same change. It wants a provider row to hang a policy off, not a working vendor, and it was pointing a dummy key at the real api.openai.com — which the credential check now correctly refuses. A private address is left unchecked whether or not the run has vendor keys, and covers that path while it is there.
This commit is contained in:
@@ -202,7 +202,15 @@ func (c *Client) discoveryURL(entry catalog.Provider, req Request) (string, erro
|
||||
|
||||
target := &url.URL{Scheme: "https", Host: host, Path: entry.Discovery.Path, RawQuery: entry.Discovery.Query}
|
||||
if err := c.checkPublicHost(target.Hostname()); err != nil {
|
||||
return "", err
|
||||
// A host that refuses to resolve fails here, before any request is
|
||||
// built, and it is the commonest way for an upstream to be wrong. It
|
||||
// has to reach the caller as unreachable rather than as an
|
||||
// unclassified fault. ErrPrivateHost is the other outcome and means
|
||||
// something else entirely — not a bad host, one we decline to dial.
|
||||
if errors.Is(err, ErrPrivateHost) {
|
||||
return "", err
|
||||
}
|
||||
return "", &UnreachableError{Provider: entry.Name, Err: err}
|
||||
}
|
||||
return target.String(), nil
|
||||
}
|
||||
|
||||
@@ -2,7 +2,9 @@ package modeldiscovery
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"io"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/netip"
|
||||
@@ -530,3 +532,32 @@ func TestBedrockProfilesFromAnyGeographyArrivePriced(t *testing.T) {
|
||||
// only form that works at invoke time.
|
||||
assert.Equal(t, "jp.anthropic.claude-sonnet-5-20260514-v1:0", models[0].ID)
|
||||
}
|
||||
|
||||
// TestFetch_AHostThatWillNotResolveIsUnreachable closes a gap the live suite
|
||||
// found. The SSRF guard resolves the host before any request is built, so a
|
||||
// name that does not resolve fails there rather than at the transport — and
|
||||
// that error used to reach the caller unclassified. A wrong hostname is the
|
||||
// commonest way for an upstream to be wrong, so it has to arrive as
|
||||
// "unreachable" and not as an unrecognised fault.
|
||||
func TestFetch_AHostThatWillNotResolveIsUnreachable(t *testing.T) {
|
||||
// A resolver whose dial always fails, so the lookup errors without the
|
||||
// test depending on real DNS.
|
||||
refusing := &net.Resolver{
|
||||
PreferGo: true,
|
||||
Dial: func(ctx context.Context, network, address string) (net.Conn, error) {
|
||||
return nil, errors.New("resolver unavailable")
|
||||
},
|
||||
}
|
||||
client := &Client{Resolver: refusing}
|
||||
|
||||
_, err := client.Fetch(context.Background(), Request{
|
||||
CatalogID: "openai_api",
|
||||
UpstreamURL: "https://not-a-real-vendor-host.example.invalid",
|
||||
APIKey: "sk-test",
|
||||
})
|
||||
|
||||
require.Error(t, err)
|
||||
var unreachable *UnreachableError
|
||||
require.ErrorAs(t, err, &unreachable, "a host that will not resolve must classify as unreachable")
|
||||
require.NotErrorIs(t, err, ErrPrivateHost, "it is not a host we declined to dial")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user