feat(agent): ACL 渲染放行/拒绝规则对,空白名单仍拒绝
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package agentd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
@@ -173,3 +174,85 @@ func TestConfigACLPaths(t *testing.T) {
|
||||
t.Errorf("ACLLastGoodPath() = %q, want %q", c.ACLLastGoodPath(), want)
|
||||
}
|
||||
}
|
||||
|
||||
// rules() 必须产出「先全部放行、再全部拒绝」,且同一 target 两侧目的地条件逐字相同。
|
||||
func TestACLRules_AllowThenDeny(t *testing.T) {
|
||||
ac := &ACLConfig{
|
||||
Enabled: true,
|
||||
AllowDpUUIDs: []string{"uuid-me"},
|
||||
Targets: []ACLTarget{
|
||||
{Domain: []string{"brain.51yanmei.com"}},
|
||||
{IPCIDR: []string{"182.92.213.171/32"}, Port: []int{5001}},
|
||||
},
|
||||
}
|
||||
rules := ac.rules()
|
||||
if len(rules) != 4 {
|
||||
t.Fatalf("规则数 = %d, want 4 (2 target × 放行+拒绝)", len(rules))
|
||||
}
|
||||
|
||||
// 前两条是放行:带 user + outbound,不带 action
|
||||
for i := 0; i < 2; i++ {
|
||||
r := rules[i].(map[string]any)
|
||||
if _, ok := r["user"]; !ok {
|
||||
t.Errorf("rules[%d] 放行规则缺 user", i)
|
||||
}
|
||||
if r["outbound"] != directOutboundTag {
|
||||
t.Errorf("rules[%d] outbound = %v, want %q", i, r["outbound"], directOutboundTag)
|
||||
}
|
||||
if _, ok := r["action"]; ok {
|
||||
t.Errorf("rules[%d] 放行规则不应带 action", i)
|
||||
}
|
||||
}
|
||||
// 后两条是拒绝:带 action=reject,不带 user(对所有人生效)
|
||||
for i := 2; i < 4; i++ {
|
||||
r := rules[i].(map[string]any)
|
||||
if r["action"] != "reject" {
|
||||
t.Errorf("rules[%d] action = %v, want reject", i, r["action"])
|
||||
}
|
||||
if _, ok := r["user"]; ok {
|
||||
t.Errorf("rules[%d] 拒绝规则不应带 user,否则会漏掉名单外的人", i)
|
||||
}
|
||||
}
|
||||
|
||||
// 对称性:target[0] 的放行(rules[0])与拒绝(rules[2])目的地条件必须逐字相同
|
||||
allow0 := rules[0].(map[string]any)
|
||||
deny0 := rules[2].(map[string]any)
|
||||
if fmt.Sprint(allow0["domain"]) != fmt.Sprint(deny0["domain"]) {
|
||||
t.Errorf("target0 放行/拒绝的 domain 不一致: %v vs %v", allow0["domain"], deny0["domain"])
|
||||
}
|
||||
allow1 := rules[1].(map[string]any)
|
||||
deny1 := rules[3].(map[string]any)
|
||||
if fmt.Sprint(allow1["ip_cidr"]) != fmt.Sprint(deny1["ip_cidr"]) ||
|
||||
fmt.Sprint(allow1["port"]) != fmt.Sprint(deny1["port"]) {
|
||||
t.Error("target1 放行/拒绝的 ip_cidr/port 不一致")
|
||||
}
|
||||
}
|
||||
|
||||
// 空白名单 → 不产出放行规则,但拒绝规则照出(fail-closed 的核心断言)。
|
||||
func TestACLRules_EmptyAllowlistStillDenies(t *testing.T) {
|
||||
ac := &ACLConfig{
|
||||
Enabled: true,
|
||||
AllowDpUUIDs: nil,
|
||||
Targets: []ACLTarget{{Domain: []string{"brain.51yanmei.com"}}},
|
||||
}
|
||||
rules := ac.rules()
|
||||
if len(rules) != 1 {
|
||||
t.Fatalf("规则数 = %d, want 1 (仅拒绝)", len(rules))
|
||||
}
|
||||
r := rules[0].(map[string]any)
|
||||
if r["action"] != "reject" {
|
||||
t.Errorf("action = %v, want reject", r["action"])
|
||||
}
|
||||
}
|
||||
|
||||
// 未 active(含 nil / enabled=false)→ 无规则。
|
||||
func TestACLRules_InactiveYieldsNil(t *testing.T) {
|
||||
var nilACL *ACLConfig
|
||||
if got := nilACL.rules(); got != nil {
|
||||
t.Errorf("nil ACL rules() = %v, want nil", got)
|
||||
}
|
||||
off := &ACLConfig{Enabled: false, Targets: []ACLTarget{{Domain: []string{"a.com"}}}}
|
||||
if got := off.rules(); got != nil {
|
||||
t.Errorf("enabled=false rules() = %v, want nil", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user