Files
releases/v1/topdown/net_test.go
T
Ville Vesilehto f77322b3fb build: bump Go version requirement to 1.24 (#7839)
Go 1.23 is no longer supported as per Go release policy.

Changes:

- Use Go v1.24.6 as the project SDK requirement
- Apply lint fixes for Go 1.24
- Fix "non-constant format string in call" issues as seen in CI.

Signed-off-by: Ville Vesilehto <ville@vesilehto.fi>
2025-08-24 09:02:09 +02:00

223 lines
5.6 KiB
Go

// Copyright 2021 The OPA Authors. All rights reserved.
// Use of this source code is governed by an Apache2
// license that can be found in the LICENSE file.
//go:build !race
// +build !race
package topdown
import (
"context"
"errors"
"fmt"
"testing"
"time"
"github.com/foxcpp/go-mockdns"
"github.com/open-policy-agent/opa/v1/ast"
"github.com/open-policy-agent/opa/v1/topdown/builtins"
)
// TestNetLookupIPAddr replaces the resolver used by builtinLookupIPAddr.
// Due to some intricacies of the net/LookupIP internals, it seems impossible
// to do that in a way that passes the race detector.
func TestNetLookupIPAddr(t *testing.T) {
t.Parallel()
srv, err := mockdns.NewServerWithLogger(map[string]mockdns.Zone{
"v4.org.": {
A: []string{"1.2.3.4"},
},
"v6.org.": {
AAAA: []string{"1:2:3::4"},
},
"v4-v6.org.": {
A: []string{"1.2.3.4"},
AAAA: []string{"1:2:3::4"},
},
"error.org.": {
Err: errors.New("OH NO"),
},
}, sink{}, true)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { srv.Close() })
srvFail, err := mockdns.NewServerWithLogger(map[string]mockdns.Zone{}, sink{}, true)
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { srvFail.Close() })
t.Cleanup(func() { mockdns.UnpatchNet(resolv) })
for addr, exp := range map[string]ast.Set{
"v4.org": ast.NewSet(ast.StringTerm("1.2.3.4")),
"v6.org": ast.NewSet(ast.StringTerm("1:2:3::4")),
"v4-v6.org": ast.NewSet(ast.StringTerm("1.2.3.4"), ast.StringTerm("1:2:3::4")),
} {
t.Run(addr, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
bctx := BuiltinContext{
Context: ctx,
Cache: make(builtins.Cache),
}
srv.PatchNet(resolv)
err := builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm(addr)}, func(act *ast.Term) error {
if exp.Compare(act.Value) != 0 {
t.Errorf("expected %v, got %v", exp, act)
}
return nil
})
if err != nil {
t.Error(err)
}
// check cache put
act, ok := bctx.Cache.Get(lookupIPAddrCacheKey(addr))
if !ok {
t.Fatal("result not put into cache")
}
if exp.Compare(act.(*ast.Term).Value) != 0 {
t.Errorf("cache: expected %v, got %v", exp, act)
}
// exercise cache hit
srvFail.PatchNet(resolv)
err = builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm(addr)}, func(act *ast.Term) error {
if exp.Compare(act.Value) != 0 {
t.Errorf("expected %v, got %v", exp, act)
}
return nil
})
if err != nil {
t.Error(err)
}
})
}
for _, addr := range []string{"error.org", "nosuch.org"} {
t.Run(addr, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
bctx := BuiltinContext{
Context: ctx,
Cache: make(builtins.Cache),
}
srv.PatchNet(resolv)
err := builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm(addr)}, func(*ast.Term) error {
t.Fatal("expected not to be called")
return nil
})
if err == nil {
t.Error("expected error")
}
if testing.Verbose() {
t.Log(err)
}
})
}
cancelled := func() (context.Context, func()) {
ctx, cancel := context.WithCancel(t.Context())
cancel()
return ctx, cancel
}
timedOut := func() (context.Context, func()) {
return context.WithTimeout(t.Context(), time.Nanosecond)
}
for name, ctx := range map[string]func() (context.Context, func()){
"cancelled": cancelled,
"timed out": timedOut,
} {
t.Run(name, func(t *testing.T) {
ctx, cancel := ctx()
defer cancel()
bctx := BuiltinContext{
Context: ctx,
Cache: make(builtins.Cache),
}
srv.PatchNet(resolv)
err := builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm("example.org")}, func(*ast.Term) error {
t.Fatal("expected not to be called")
return nil
})
if err == nil {
t.Fatal("expected error")
}
_, ok := err.(Halt)
if !ok {
t.Errorf("expected Halt error, got %v (%[1]T)", err)
}
if !IsCancel(err) {
t.Errorf("expected wrapped Cancel error, got %v (%[1]T)", err)
}
})
}
addr := "v4.org"
exp := ast.NewSet(ast.StringTerm("1.2.3.4"))
for name, allowNet := range map[string][]string{
"allow_net nil": nil,
"allow_net match": {addr},
"allow_net match + additional host": {addr, "example.com"},
} {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
capabilities := ast.CapabilitiesForThisVersion()
capabilities.AllowNet = allowNet
bctx := BuiltinContext{
Context: ctx,
Cache: make(builtins.Cache),
Capabilities: capabilities,
}
srv.PatchNet(resolv)
err := builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm(addr)}, func(act *ast.Term) error {
if exp.Compare(act.Value) != 0 {
t.Errorf("expected %v, got %v", exp, act)
}
return nil
})
if err != nil {
t.Error(err)
}
})
}
expError := fmt.Errorf("unallowed host: %s", addr)
for name, allowNet := range map[string][]string{
"allow_net empty": {},
"allow_net no match": {"example.com"},
} {
t.Run(name, func(t *testing.T) {
ctx, cancel := context.WithCancel(t.Context())
defer cancel()
capabilities := ast.CapabilitiesForThisVersion()
capabilities.AllowNet = allowNet
bctx := BuiltinContext{
Context: ctx,
Cache: make(builtins.Cache),
Capabilities: capabilities,
}
srv.PatchNet(resolv)
err := builtinLookupIPAddr(bctx, []*ast.Term{ast.StringTerm(addr)}, func(_ *ast.Term) error {
t.Fatal("expected not to be called")
return nil
})
if err == nil {
t.Error("expected error")
}
assertError(t, expError, err)
})
}
}
type sink struct{}
func (sink) Printf(string, ...any) {}