mirror of
https://github.com/netbirdio/netbird.git
synced 2026-10-13 08:59:08 +02:00
[proxy,management] Bound model discovery to the caller's own policies (#7239)
[proxy,management] Bound model discovery to the caller's own policies GET /v1/models was bounded by the provider record's enumerated models, which is the right bound only while a single policy reaches a provider. Where two teams share one provider under different allowlists, every caller was offered the union — each model outside their own policy being a request the guardrail refuses a moment later. A gateway record enumerating nothing was worse: it offered the upstream's entire catalogue however narrow the policy was. Each route now carries one rule per authorising policy — its source groups and the models it permits — instead of a single flattened list. At request time the router keeps the rules whose groups intersect the caller's, unions their models, and intersects that with what the provider serves. nil and [] stay distinct end to end: a policy setting no allowlist reaches the router as nil and lifts the restriction for the groups it binds, while an enabled allowlist with no models arrives as [] and permits nothing. Collapsing them would let a listing that should offer nothing fall open to everything. The guardrail's own per-provider allowlist is untouched. It is a fail-closed backstop that cannot tell who is asking, so discovery is now narrower than the backstop rather than wider.
This commit is contained in:
+25
-11
@@ -200,12 +200,18 @@ func (cl *Client) pollStatus(ctx context.Context, timeout time.Duration, want st
|
||||
const (
|
||||
// curlExitCouldNotResolve is curl's exit code for a DNS resolution failure, distinct from connection-level failures.
|
||||
curlExitCouldNotResolve = 6
|
||||
// dnsProbeRetryWindow bounds DNS-failure retries: the synthesized zone lands a beat after management connects, so early NXDOMAIN is propagation; a zone still absent after this window is a real failure.
|
||||
dnsProbeRetryWindow = 30 * time.Second
|
||||
dnsProbeRetryInterval = 2 * time.Second
|
||||
// curlExitCouldNotConnect is curl's exit code for a connection that never
|
||||
// established. The probe exists to WAKE the lazy proxy peer, so the first
|
||||
// attempt legitimately arrives before WireGuard has brought the tunnel up
|
||||
// and fails here — which is propagation, exactly like an early NXDOMAIN,
|
||||
// and belongs inside the retry window rather than failing the test outright.
|
||||
curlExitCouldNotConnect = 7
|
||||
// endpointProbeRetryWindow bounds retries of the transient failures above: the synthesized zone and the tunnel both land a beat after management connects. Still failing after this window is a real failure.
|
||||
endpointProbeRetryWindow = 30 * time.Second
|
||||
endpointProbeRetryInterval = 2 * time.Second
|
||||
)
|
||||
|
||||
// ResolveProxyIP GETs https://<endpoint>/ from the client's netns: any HTTP status proves DNS + tunnel and wakes the lazy proxy peer; only DNS failures retry, within dnsProbeRetryWindow. Returns the connected IP for --resolve pinning.
|
||||
// ResolveProxyIP GETs https://<endpoint>/ from the client's netns: any HTTP status proves DNS + tunnel and wakes the lazy proxy peer; DNS and connect failures retry, within endpointProbeRetryWindow. Returns the connected IP for --resolve pinning.
|
||||
func (cl *Client) ResolveProxyIP(ctx context.Context, endpoint string) (string, error) {
|
||||
args := []string{
|
||||
"run", "--rm",
|
||||
@@ -216,7 +222,7 @@ func (cl *Client) ResolveProxyIP(ctx context.Context, endpoint string) (string,
|
||||
"-w", "%{remote_ip}",
|
||||
"https://" + endpoint + "/",
|
||||
}
|
||||
deadline := time.Now().Add(dnsProbeRetryWindow)
|
||||
deadline := time.Now().Add(endpointProbeRetryWindow)
|
||||
for {
|
||||
cmd := exec.CommandContext(ctx, "docker", args...)
|
||||
var stdout, stderr strings.Builder
|
||||
@@ -232,21 +238,29 @@ func (cl *Client) ResolveProxyIP(ctx context.Context, endpoint string) (string,
|
||||
}
|
||||
|
||||
var exitErr *exec.ExitError
|
||||
if !errors.As(err, &exitErr) || exitErr.ExitCode() != curlExitCouldNotResolve {
|
||||
if !errors.As(err, &exitErr) || !isTransientProbeExit(exitErr.ExitCode()) {
|
||||
return "", fmt.Errorf("no HTTP response from %s: %w (%s)", endpoint, err, strings.TrimSpace(stderr.String()))
|
||||
}
|
||||
dnsErr := fmt.Errorf("DNS resolution failed for %s: %s", endpoint, strings.TrimSpace(stderr.String()))
|
||||
if time.Until(deadline) < dnsProbeRetryInterval {
|
||||
return "", dnsErr
|
||||
probeErr := fmt.Errorf("endpoint %s not reachable yet: %s", endpoint, strings.TrimSpace(stderr.String()))
|
||||
if time.Until(deadline) < endpointProbeRetryInterval {
|
||||
return "", probeErr
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return "", fmt.Errorf("%w (%w)", dnsErr, ctx.Err())
|
||||
case <-time.After(dnsProbeRetryInterval):
|
||||
return "", fmt.Errorf("%w (%w)", probeErr, ctx.Err())
|
||||
case <-time.After(endpointProbeRetryInterval):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// isTransientProbeExit reports whether a curl exit code describes a state the
|
||||
// endpoint is expected to pass THROUGH on its way up, rather than a settled
|
||||
// failure. Anything else — TLS refusal, a protocol error, a bad argument —
|
||||
// would still be failing after the retry window, so it fails immediately.
|
||||
func isTransientProbeExit(code int) bool {
|
||||
return code == curlExitCouldNotResolve || code == curlExitCouldNotConnect
|
||||
}
|
||||
|
||||
// Wire shapes for Chat.
|
||||
const (
|
||||
// WireChat is the OpenAI-compatible /v1/chat/completions shape.
|
||||
|
||||
Reference in New Issue
Block a user