mirror of
https://github.com/langgenius/dify.git
synced 2026-11-12 14:21:29 +08:00
Track backend integration, Ruff cleanup and migration reconciliation in ENG-1271. Retain required copy contracts without implementing frontend interactions.
128 lines
3.9 KiB
Go
128 lines
3.9 KiB
Go
//go:build linux
|
|
|
|
package testscope
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"io"
|
|
"net"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"net/netip"
|
|
"net/url"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestBrokerRejectsControlAndMetadataTargets(t *testing.T) {
|
|
for _, ip := range []string{"127.0.0.1", "10.0.0.1", "169.254.169.254", "::1", "::ffff:127.0.0.1"} {
|
|
if AllowedAddress(netip.MustParseAddr(ip), nil) {
|
|
t.Fatalf("allowed private destination %s", ip)
|
|
}
|
|
}
|
|
}
|
|
func TestBrokerMatchesExactAuthority(t *testing.T) {
|
|
target := NetworkTarget{Host: "example.test", Port: 443}
|
|
for _, bad := range []string{"example.test:80", "other.test:443", "example.test.evil:443", "user@example.test:443"} {
|
|
if target.Matches(bad) {
|
|
t.Fatalf("accepted %s", bad)
|
|
}
|
|
}
|
|
if !target.Matches("example.test:443") {
|
|
t.Fatal("refused exact approved authority")
|
|
}
|
|
}
|
|
func TestBrokerPinsResolutionAndRejectsUnapprovedAuthority(t *testing.T) {
|
|
policy := PinnedNetwork{Targets: map[string]PinnedTarget{"example.test:443": {Addresses: []string{"93.184.216.34:443"}, MaxBytes: 128, TimeoutSeconds: 1}}}
|
|
if _, err := policy.Resolve("evil.test:443"); err == nil {
|
|
t.Fatal("unapproved authority resolved")
|
|
}
|
|
address, err := policy.Resolve("example.test:443")
|
|
if err != nil || address != "93.184.216.34:443" {
|
|
t.Fatal(address, err)
|
|
}
|
|
}
|
|
|
|
func TestBrokerRealRelayDenialBudgetAndRevocation(t *testing.T) {
|
|
var calls atomic.Int64
|
|
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { calls.Add(1); _, _ = w.Write([]byte("synthetic-canary")) }))
|
|
defer upstream.Close()
|
|
address := strings.TrimPrefix(upstream.URL, "http://")
|
|
socket := filepath.Join(t.TempDir(), "broker.sock")
|
|
var used atomic.Int64
|
|
broker, err := StartBroker(socket, PinnedNetwork{Targets: map[string]PinnedTarget{"canary.test:80": {Addresses: []string{address}, MaxBytes: 1024, TimeoutSeconds: 1}}}, time.Now().Add(time.Minute), func(_ string, n int64) error {
|
|
for {
|
|
old := used.Load()
|
|
if old+n > 1024 {
|
|
return errors.New("budget")
|
|
}
|
|
if used.CompareAndSwap(old, old+n) {
|
|
return nil
|
|
}
|
|
}
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer broker.Close()
|
|
broker.SetPeer(os.Getpid())
|
|
client := &http.Client{Timeout: 2 * time.Second, Transport: &http.Transport{Proxy: http.ProxyURL(&url.URL{Scheme: "http", Host: "broker"}), DisableKeepAlives: true, DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) {
|
|
return (&net.Dialer{}).DialContext(ctx, "unix", socket)
|
|
}}}
|
|
denied, err := client.Get("http://other.test:80/")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
_ = denied.Body.Close()
|
|
if denied.StatusCode != 403 || calls.Load() != 0 {
|
|
t.Fatal("unapproved request reached canary")
|
|
}
|
|
response, err := client.Get("http://canary.test:80/")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
body, err := io.ReadAll(response.Body)
|
|
_ = response.Body.Close()
|
|
if err != nil || string(body) != "synthetic-canary" || calls.Load() != 1 {
|
|
t.Fatal("approved relay failed", string(body), err)
|
|
}
|
|
used.Store(1024)
|
|
response, err = client.Get("http://canary.test:80/")
|
|
if err == nil {
|
|
_ = response.Body.Close()
|
|
if response.StatusCode != 502 {
|
|
t.Fatal("budget did not stop relay")
|
|
}
|
|
}
|
|
if calls.Load() != 1 {
|
|
t.Fatal("exhausted request reached target")
|
|
}
|
|
broker.Close()
|
|
if response, err = client.Get("http://canary.test:80/"); err == nil {
|
|
_ = response.Body.Close()
|
|
t.Fatal("revoked broker remained reachable")
|
|
}
|
|
}
|
|
|
|
type countedReadConn struct {
|
|
net.Conn
|
|
reads int
|
|
}
|
|
|
|
func (c *countedReadConn) Read(p []byte) (int, error) { c.reads++; p[0] = 'x'; return 1, nil }
|
|
func TestNetworkBudgetRefusalCannotConsumeUnchargedResponseBytes(t *testing.T) {
|
|
raw := &countedReadConn{}
|
|
conn := &budgetConn{Conn: raw, spend: func(int64) error { return errors.New("exhausted") }}
|
|
if _, err := conn.Read(make([]byte, 4096)); err == nil {
|
|
t.Fatal("accepted exhausted read")
|
|
}
|
|
if raw.reads != 0 {
|
|
t.Fatal("consumed upstream bytes before durable reservation")
|
|
}
|
|
}
|