Enhance parseCIDR in utils/ipallowlist.go to support single IP addresses (without CIDR prefix) and ensure IP is in canonical form. This matches the functionality previously in access.go. - Add ParseCIDR as public function supporting CIDR and single IP - Update access.go to use utils.ParseCIDR instead of local implementation - Remove duplicate parseCIDR function from access.go - Update tests to use utils.ParseCIDR Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
355 lines
7.0 KiB
Go
355 lines
7.0 KiB
Go
// Package security 提供访问控制功能的测试。
|
||
//
|
||
// 该文件测试访问控制模块的各项功能,包括:
|
||
// - 访问控制器创建
|
||
// - IP 地址检查
|
||
// - 允许列表和拒绝列表
|
||
// - CIDR 解析
|
||
// - 列表更新和统计
|
||
//
|
||
// 作者:xfy
|
||
package security
|
||
|
||
import (
|
||
"net"
|
||
"testing"
|
||
|
||
"github.com/valyala/fasthttp"
|
||
"rua.plus/lolly/internal/config"
|
||
"rua.plus/lolly/internal/utils"
|
||
)
|
||
|
||
func TestNewAccessControl(t *testing.T) {
|
||
tests := []struct {
|
||
cfg *config.AccessConfig
|
||
name string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "nil config",
|
||
cfg: nil,
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "empty config",
|
||
cfg: &config.AccessConfig{},
|
||
},
|
||
{
|
||
name: "valid allow list",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"192.168.1.0/24", "10.0.0.1"},
|
||
},
|
||
},
|
||
{
|
||
name: "valid deny list",
|
||
cfg: &config.AccessConfig{
|
||
Deny: []string{"192.168.2.100/32"},
|
||
},
|
||
},
|
||
{
|
||
name: "invalid CIDR",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"invalid"},
|
||
},
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "default allow",
|
||
cfg: &config.AccessConfig{
|
||
Default: "allow",
|
||
},
|
||
},
|
||
{
|
||
name: "default deny",
|
||
cfg: &config.AccessConfig{
|
||
Default: "deny",
|
||
},
|
||
},
|
||
{
|
||
name: "invalid default",
|
||
cfg: &config.AccessConfig{
|
||
Default: "invalid",
|
||
},
|
||
wantErr: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ac, err := NewAccessControl(tt.cfg)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("NewAccessControl() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
if !tt.wantErr && ac == nil {
|
||
t.Error("Expected non-nil AccessControl")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestAccessControlCheck(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
cfg *config.AccessConfig
|
||
ip string
|
||
expected bool
|
||
}{
|
||
{
|
||
name: "default allow",
|
||
cfg: &config.AccessConfig{
|
||
Default: "allow",
|
||
},
|
||
ip: "192.168.1.100",
|
||
expected: true,
|
||
},
|
||
{
|
||
name: "default deny",
|
||
cfg: &config.AccessConfig{
|
||
Default: "deny",
|
||
},
|
||
ip: "192.168.1.100",
|
||
expected: false,
|
||
},
|
||
{
|
||
name: "explicit allow",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"192.168.1.0/24"},
|
||
Default: "deny",
|
||
},
|
||
ip: "192.168.1.100",
|
||
expected: true,
|
||
},
|
||
{
|
||
name: "not in allow list",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"192.168.1.0/24"},
|
||
Default: "deny",
|
||
},
|
||
ip: "192.168.2.100",
|
||
expected: false,
|
||
},
|
||
{
|
||
name: "explicit deny",
|
||
cfg: &config.AccessConfig{
|
||
Deny: []string{"192.168.2.100"},
|
||
Default: "allow",
|
||
},
|
||
ip: "192.168.2.100",
|
||
expected: false,
|
||
},
|
||
{
|
||
name: "deny takes precedence",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"192.168.0.0/16"},
|
||
Deny: []string{"192.168.2.100"},
|
||
Default: "deny",
|
||
},
|
||
ip: "192.168.2.100",
|
||
expected: false,
|
||
},
|
||
{
|
||
name: "single IP allow",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"10.0.0.1"},
|
||
Default: "deny",
|
||
},
|
||
ip: "10.0.0.1",
|
||
expected: true,
|
||
},
|
||
{
|
||
name: "IPv6 allow",
|
||
cfg: &config.AccessConfig{
|
||
Allow: []string{"2001:db8::/32"},
|
||
Default: "deny",
|
||
},
|
||
ip: "2001:db8::1",
|
||
expected: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
ac, err := NewAccessControl(tt.cfg)
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
ip := net.ParseIP(tt.ip)
|
||
if ip == nil {
|
||
t.Fatalf("Invalid IP: %s", tt.ip)
|
||
}
|
||
|
||
result := ac.Check(ip)
|
||
if result != tt.expected {
|
||
t.Errorf("Check(%s) = %v, expected %v", tt.ip, result, tt.expected)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestAccessControlProcess(t *testing.T) {
|
||
ac, err := NewAccessControl(&config.AccessConfig{
|
||
Allow: []string{"127.0.0.1"},
|
||
Default: "deny",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
// Create a simple handler
|
||
nextHandler := func(ctx *fasthttp.RequestCtx) {
|
||
_, _ = ctx.WriteString("OK")
|
||
}
|
||
|
||
handler := ac.Process(nextHandler)
|
||
|
||
// Verify the handler is created correctly
|
||
if handler == nil {
|
||
t.Error("Process() returned nil handler")
|
||
}
|
||
}
|
||
|
||
func TestParseCIDR(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
cidr string
|
||
wantErr bool
|
||
}{
|
||
{
|
||
name: "valid IPv4 CIDR",
|
||
cidr: "192.168.1.0/24",
|
||
},
|
||
{
|
||
name: "valid IPv4 single",
|
||
cidr: "192.168.1.1",
|
||
},
|
||
{
|
||
name: "valid IPv6 CIDR",
|
||
cidr: "2001:db8::/32",
|
||
},
|
||
{
|
||
name: "valid IPv6 single",
|
||
cidr: "2001:db8::1",
|
||
},
|
||
{
|
||
name: "invalid IP",
|
||
cidr: "invalid",
|
||
wantErr: true,
|
||
},
|
||
{
|
||
name: "invalid CIDR",
|
||
cidr: "192.168.1.0/33",
|
||
wantErr: true,
|
||
},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
network, err := utils.ParseCIDR(tt.cidr)
|
||
if (err != nil) != tt.wantErr {
|
||
t.Errorf("utils.ParseCIDR() error = %v, wantErr %v", err, tt.wantErr)
|
||
}
|
||
if !tt.wantErr && network == nil {
|
||
t.Error("Expected non-nil network")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestUpdateAllowList(t *testing.T) {
|
||
ac, err := NewAccessControl(&config.AccessConfig{
|
||
Default: "deny",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
// Update allow list
|
||
err = ac.UpdateAllowList([]string{"10.0.0.0/8"})
|
||
if err != nil {
|
||
t.Errorf("UpdateAllowList() error: %v", err)
|
||
}
|
||
|
||
// Check that IP is now allowed
|
||
ip := net.ParseIP("10.0.0.1")
|
||
if !ac.Check(ip) {
|
||
t.Error("Expected IP to be allowed after update")
|
||
}
|
||
|
||
// Test invalid update
|
||
err = ac.UpdateAllowList([]string{"invalid"})
|
||
if err == nil {
|
||
t.Error("Expected error for invalid CIDR")
|
||
}
|
||
}
|
||
|
||
func TestUpdateDenyList(t *testing.T) {
|
||
ac, err := NewAccessControl(&config.AccessConfig{
|
||
Allow: []string{"0.0.0.0/0"},
|
||
Default: "allow",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
// Update deny list
|
||
err = ac.UpdateDenyList([]string{"192.168.2.0/24"})
|
||
if err != nil {
|
||
t.Errorf("UpdateDenyList() error: %v", err)
|
||
}
|
||
|
||
// Check that IP is now denied
|
||
ip := net.ParseIP("192.168.2.1")
|
||
if ac.Check(ip) {
|
||
t.Error("Expected IP to be denied after update")
|
||
}
|
||
}
|
||
|
||
func TestSetDefault(t *testing.T) {
|
||
ac, err := NewAccessControl(&config.AccessConfig{
|
||
Default: "allow",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
// Change to deny
|
||
err = ac.SetDefault("deny")
|
||
if err != nil {
|
||
t.Errorf("SetDefault() error: %v", err)
|
||
}
|
||
|
||
stats := ac.GetStats()
|
||
if stats.Default != "deny" {
|
||
t.Errorf("Expected default 'deny', got %s", stats.Default)
|
||
}
|
||
|
||
// Test invalid action
|
||
err = ac.SetDefault("invalid")
|
||
if err == nil {
|
||
t.Error("Expected error for invalid action")
|
||
}
|
||
}
|
||
|
||
func TestGetStats(t *testing.T) {
|
||
ac, err := NewAccessControl(&config.AccessConfig{
|
||
Allow: []string{"192.168.1.0/24", "10.0.0.0/8"},
|
||
Deny: []string{"192.168.2.100"},
|
||
Default: "deny",
|
||
})
|
||
if err != nil {
|
||
t.Fatalf("NewAccessControl() error: %v", err)
|
||
}
|
||
|
||
stats := ac.GetStats()
|
||
if stats.AllowCount != 2 {
|
||
t.Errorf("Expected AllowCount 2, got %d", stats.AllowCount)
|
||
}
|
||
if stats.DenyCount != 1 {
|
||
t.Errorf("Expected DenyCount 1, got %d", stats.DenyCount)
|
||
}
|
||
if stats.Default != "deny" {
|
||
t.Errorf("Expected Default 'deny', got %s", stats.Default)
|
||
}
|
||
}
|