Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -356,7 +356,7 @@ The `cli` package exports many types used across its command implementations. Th
| `PerformanceMetrics` | struct | Performance counters for a workflow run |
| `PolicyAnalysis` | struct | Analysis of guard-policy evaluation results |
| `PolicyManifest` | struct | A manifest of guard policies applied during a run |
| `PolicyRule` | struct | A single firewall policy rule from the policy manifest |
| `FirewallPolicyRule` | struct | A single firewall policy rule from the policy manifest |
| `PolicySummaryDisplay` | struct | Display-friendly summary of policy evaluation results |
| `PollResult` | int alias | Result code returned by `PollWithSignalHandling` |
| `ProcessedRun` | struct | A fully-processed workflow run with parsed artifacts |
Expand Down
34 changes: 17 additions & 17 deletions pkg/cli/firewall_policy.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,19 @@ var firewallPolicyLog = logger.New("cli:firewall_policy")
// PolicyManifest represents the policy-manifest.json file generated by AWF.
// It describes the firewall policy rules and configuration used during a workflow run.
type PolicyManifest struct {
Version int `json:"version"`
GeneratedAt string `json:"generatedAt"`
Rules []PolicyRule `json:"rules"`
DangerousPorts []int `json:"dangerousPorts,omitempty"`
DNSServers []string `json:"dnsServers,omitempty"`
SSLBumpEnabled bool `json:"sslBumpEnabled"`
DLPEnabled bool `json:"dlpEnabled"`
HostAccessEnabled bool `json:"hostAccessEnabled"`
AllowHostPorts *string `json:"allowHostPorts"` // nullable → pointer
Version int `json:"version"`
GeneratedAt string `json:"generatedAt"`
Rules []FirewallPolicyRule `json:"rules"`
DangerousPorts []int `json:"dangerousPorts,omitempty"`
DNSServers []string `json:"dnsServers,omitempty"`
SSLBumpEnabled bool `json:"sslBumpEnabled"`
DLPEnabled bool `json:"dlpEnabled"`
HostAccessEnabled bool `json:"hostAccessEnabled"`
AllowHostPorts *string `json:"allowHostPorts"` // nullable → pointer
}

// PolicyRule represents a single firewall policy rule from the manifest.
type PolicyRule struct {
// FirewallPolicyRule represents a single firewall policy rule from the manifest.
type FirewallPolicyRule struct {
ID string `json:"id"`
Order int `json:"order"`
Action string `json:"action"` // "allow" or "deny"
Expand Down Expand Up @@ -71,8 +71,8 @@ type EnrichedRequest struct {

// RuleHitStats tracks hit counts per policy rule.
type RuleHitStats struct {
Rule PolicyRule `json:"rule"`
Hits int `json:"hits"`
Rule FirewallPolicyRule `json:"rule"`
Hits int `json:"hits"`
}

// PolicyAnalysis contains the enriched policy analysis results.
Expand Down Expand Up @@ -101,7 +101,7 @@ func loadPolicyManifest(manifestPath string) (*PolicyManifest, error) {
}

// Sort rules by order for deterministic matching
slices.SortFunc(manifest.Rules, func(a, b PolicyRule) int {
slices.SortFunc(manifest.Rules, func(a, b FirewallPolicyRule) int {
if a.Order < b.Order {
return -1
}
Expand Down Expand Up @@ -162,7 +162,7 @@ func parseAuditJSONL(jsonlPath string) ([]AuditLogEntry, error) {
// - "github.com" matches only exactly "github.com"
// - Regex patterns (detected via aclName containing "regex" or regex metacharacters) are
// compiled and matched against the domain.
func domainMatchesRule(host string, rule PolicyRule) bool {
func domainMatchesRule(host string, rule FirewallPolicyRule) bool {
// Strip port from host if present
domain := host
if idx := strings.LastIndex(host, ":"); idx != -1 {
Expand Down Expand Up @@ -256,7 +256,7 @@ func isEntryAllowed(entry AuditLogEntry) bool {

// protocolMatches checks if a rule's protocol constraint matches the request.
// "both" matches everything; "https" matches only HTTPS; "http" matches only HTTP.
func protocolMatches(rule PolicyRule, isHTTPS bool) bool {
func protocolMatches(rule FirewallPolicyRule, isHTTPS bool) bool {
switch strings.ToLower(rule.Protocol) {
case "https":
return isHTTPS
Expand All @@ -276,7 +276,7 @@ func protocolMatches(rule PolicyRule, isHTTPS bool) bool {
// 2. Protocol matching: HTTPS-only rules won't match HTTP requests, and vice versa
// 3. Observed-decision validation: a rule only "matches" if its action matches the
// observed outcome (allow rule only credited for allowed traffic, deny for denied)
func findMatchingRule(entry AuditLogEntry, rules []PolicyRule) *PolicyRule {
func findMatchingRule(entry AuditLogEntry, rules []FirewallPolicyRule) *FirewallPolicyRule {
isHTTPS := isEntryHTTPS(entry)
expectedAction := "deny"
if isEntryAllowed(entry) {
Expand Down
44 changes: 22 additions & 22 deletions pkg/cli/firewall_policy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,93 +16,93 @@ func TestDomainMatchesRule(t *testing.T) {
tests := []struct {
name string
host string
rule PolicyRule
rule FirewallPolicyRule
expected bool
}{
{
name: "exact match without port",
host: "github.com",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{"github.com"},
},
expected: true,
},
{
name: "exact match with port",
host: "github.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{"github.com"},
},
expected: true,
},
{
name: "wildcard match - subdomain",
host: "api.github.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: true,
},
{
name: "wildcard match - base domain",
host: "github.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: true,
},
{
name: "wildcard match - deep subdomain",
host: "api.v2.github.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: true,
},
{
name: "no match - different domain",
host: "evil.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: false,
},
{
name: "no match - suffix collision",
host: "notgithub.com:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: false,
},
{
name: "case insensitive match",
host: "API.GitHub.COM:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com"},
},
expected: true,
},
{
name: "multiple domains in rule",
host: "npmjs.org:443",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{".github.com", "npmjs.org"},
},
expected: true,
},
{
name: "no match - empty domains",
host: "example.com",
rule: PolicyRule{
rule: FirewallPolicyRule{
Domains: []string{},
},
expected: false,
},
{
name: "regex match - IP pattern",
host: "192.168.1.1",
rule: PolicyRule{
rule: FirewallPolicyRule{
ACLName: "dst_ipv4_regex",
Domains: []string{`^192\.168\.`},
},
Expand All @@ -111,7 +111,7 @@ func TestDomainMatchesRule(t *testing.T) {
{
name: "regex match - metachar detection",
host: "test.example.com",
rule: PolicyRule{
rule: FirewallPolicyRule{
ACLName: "some_acl",
Domains: []string{`^.*\.example\.com$`},
},
Expand All @@ -120,7 +120,7 @@ func TestDomainMatchesRule(t *testing.T) {
{
name: "regex no match",
host: "other.com",
rule: PolicyRule{
rule: FirewallPolicyRule{
ACLName: "dst_ipv4_regex",
Domains: []string{`^192\.168\.`},
},
Expand All @@ -137,7 +137,7 @@ func TestDomainMatchesRule(t *testing.T) {
}

func TestFindMatchingRule(t *testing.T) {
rules := []PolicyRule{
rules := []FirewallPolicyRule{
{
ID: "allow-github",
Order: 1,
Expand Down Expand Up @@ -211,7 +211,7 @@ func TestFindMatchingRule(t *testing.T) {
}

func TestProtocolMatching(t *testing.T) {
rules := []PolicyRule{
rules := []FirewallPolicyRule{
{
ID: "allow-https-only",
Order: 1,
Expand Down Expand Up @@ -291,7 +291,7 @@ func TestLoadPolicyManifest(t *testing.T) {
manifest := PolicyManifest{
Version: 1,
GeneratedAt: "2026-01-01T00:00:00Z",
Rules: []PolicyRule{
Rules: []FirewallPolicyRule{
{ID: "rule-b", Order: 2, Action: "deny", Domains: []string{".evil.com"}, Description: "Block evil"},
{ID: "rule-a", Order: 1, Action: "allow", Domains: []string{".github.com"}, Description: "Allow GitHub"},
},
Expand Down Expand Up @@ -321,7 +321,7 @@ func TestLoadPolicyManifest(t *testing.T) {
ports := "8080,9090"
manifest := PolicyManifest{
Version: 1,
Rules: []PolicyRule{},
Rules: []FirewallPolicyRule{},
HostAccessEnabled: true,
AllowHostPorts: &ports,
}
Expand Down Expand Up @@ -417,7 +417,7 @@ func TestEnrichWithPolicyRules(t *testing.T) {
manifest := &PolicyManifest{
Version: 1,
GeneratedAt: "2026-01-01T00:00:00Z",
Rules: []PolicyRule{
Rules: []FirewallPolicyRule{
{
ID: "allow-github",
Order: 1,
Expand Down Expand Up @@ -518,7 +518,7 @@ func TestEnrichWithPolicyRules(t *testing.T) {
// any allow rule should be classified as (unattributed-allow), not (implicit-deny)
limitedManifest := &PolicyManifest{
Version: 1,
Rules: []PolicyRule{
Rules: []FirewallPolicyRule{
{ID: "allow-github", Order: 1, Action: "allow", ACLName: "allowed_domains", Protocol: "both", Domains: []string{".github.com"}, Description: "Allow GitHub"},
},
}
Expand Down Expand Up @@ -699,7 +699,7 @@ func TestAnalyzeFirewallPolicy(t *testing.T) {
manifest := PolicyManifest{
Version: 1,
GeneratedAt: "2026-01-01T00:00:00Z",
Rules: []PolicyRule{
Rules: []FirewallPolicyRule{
{ID: "allow-github", Order: 1, Action: "allow", ACLName: "allowed_domains", Protocol: "both", Domains: []string{".github.com"}, Description: "Allow GitHub"},
{ID: "deny-all", Order: 2, Action: "deny", ACLName: "all", Protocol: "both", Domains: []string{}, Description: "Block all other traffic"},
},
Expand Down Expand Up @@ -732,7 +732,7 @@ func TestAnalyzeFirewallPolicy(t *testing.T) {

manifest := PolicyManifest{
Version: 1,
Rules: []PolicyRule{{ID: "r1", Order: 1, Action: "allow", Domains: []string{".example.com"}}},
Rules: []FirewallPolicyRule{{ID: "r1", Order: 1, Action: "allow", Domains: []string{".example.com"}}},
SSLBumpEnabled: true,
}
manifestData, err := json.Marshal(manifest)
Expand Down
Loading