Files
ITWorx-Pulse-Public/internal/probe/policy_test.go
T
ITWorx Pulse release export bd774932d5
Public source validation / validate (push) Failing after 3m8s
Publish ITWorx Pulse source
2026-09-03 02:09:19 +02:00

110 lines
4.1 KiB
Go

package probe
import (
"context"
"net/http"
"net/http/httptest"
"net/netip"
"strings"
"testing"
"github.com/itworx/pulse/internal/audit"
)
type resolverFunc func(context.Context, string) ([]netip.Addr, error)
func (f resolverFunc) LookupIP(ctx context.Context, host string) ([]netip.Addr, error) {
return f(ctx, host)
}
func privatePolicy() NetworkPolicy {
return NetworkPolicy{Revision: 1, AllowedNetworks: []netip.Prefix{netip.MustParsePrefix("10.0.0.0/8")}}
}
func TestNetworkPolicyBlocksSSRFAndAllowsExplicitLAN(t *testing.T) {
policy := privatePolicy()
resolver := resolverFunc(func(_ context.Context, host string) ([]netip.Addr, error) {
if host == "lan.internal" {
return []netip.Addr{netip.MustParseAddr("10.10.0.8")}, nil
}
return []netip.Addr{netip.MustParseAddr("169.254.169.254")}, nil
})
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", "lan.internal", 443); err != nil {
t.Fatal(err)
}
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", "metadata.internal", 443); err == nil {
t.Fatal("metadata target was allowed")
}
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", resolverHost("127.0.0.1"), 80); err == nil {
t.Fatal("loopback target was allowed")
}
}
func TestNetworkPolicyRejectsDNSRebindingAndRedirectEscape(t *testing.T) {
policy := privatePolicy()
addresses := []netip.Addr{netip.MustParseAddr("10.0.0.2")}
resolver := resolverFunc(func(_ context.Context, _ string) ([]netip.Addr, error) {
current := addresses
addresses = []netip.Addr{netip.MustParseAddr("169.254.169.254")}
return current, nil
})
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", "service.internal", 443); err != nil {
t.Fatal(err)
}
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", "service.internal", 443); err == nil {
t.Fatal("DNS rebinding to a blocked address was allowed")
}
resolver = resolverFunc(func(_ context.Context, _ string) ([]netip.Addr, error) {
return []netip.Addr{netip.MustParseAddr("10.0.0.2"), netip.MustParseAddr("169.254.169.254")}, nil
})
if _, err := policy.ValidateTarget(context.Background(), resolver, "https", "service.internal", 443); err == nil {
t.Fatal("mixed DNS answer was allowed")
}
client, err := NewSafeClient(policy, resolverFunc(func(_ context.Context, host string) ([]netip.Addr, error) {
if host == "safe.internal" {
return []netip.Addr{netip.MustParseAddr("10.0.0.2")}, nil
}
return []netip.Addr{netip.MustParseAddr("169.254.169.254")}, nil
}))
if err != nil {
t.Fatal(err)
}
redirect := httptest.NewRequest(http.MethodGet, "https://metadata.internal/", nil)
if err := client.HTTP.CheckRedirect(redirect, nil); err == nil {
t.Fatal("redirect escape was allowed")
}
}
func TestNetworkPolicyBoundsRequestsResponsesAndAudits(t *testing.T) {
policy := privatePolicy()
request := httptest.NewRequest(http.MethodPost, "https://example.com", strings.NewReader("body"))
if err := policy.ValidateRequest(request); err == nil {
t.Fatal("POST/body request was allowed")
}
request = httptest.NewRequest(http.MethodGet, "https://example.com", nil)
request.Header.Set("Authorization", "secret")
if err := policy.ValidateRequest(request); err == nil {
t.Fatal("authorization header was allowed")
}
response := &http.Response{Body: ioNopCloser{Reader: strings.NewReader("12345")}}
if _, err := ReadLimitedBody(response, 4); err == nil {
t.Fatal("oversized response was allowed")
}
store := &audit.MemoryStore{}
if err := RecordPolicyChange(context.Background(), store, "user", "policy", "corr", NetworkPolicy{Revision: 1}, policy); err != nil {
t.Fatal(err)
}
if len(store.Events) != 1 || store.Events[0].Action != "probe.network_policy.update" || store.Events[0].After["maxResponseBytes"] == nil {
t.Fatalf("audit=%+v", store.Events)
}
if err := RecordPolicyChange(context.Background(), nil, "user", "policy", "corr", policy, policy); err == nil {
t.Fatal("nil audit store was accepted")
}
}
type ioNopCloser struct{ *strings.Reader }
func (ioNopCloser) Close() error { return nil }
func resolverHost(host string) string { return host }