Skip to content

Commit 58512c6

Browse files
authored
logDrops (#10)
1 parent 94a41ae commit 58512c6

File tree

3 files changed

+645
-139
lines changed

3 files changed

+645
-139
lines changed

outbound.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type PluginConf struct {
3434
Logging LogConfig `json:"logging"`
3535
Metadata map[string]string `json:"metadata"`
3636
DryRun bool `json:"dryRun"`
37+
LogDrops bool `json:"logDrops"`
3738
}
3839

3940
func getLogAttrs() slog.Attr {
@@ -51,7 +52,7 @@ func getLogAttrs() slog.Attr {
5152
var (
5253
logger = slog.New(slog.NewTextHandler(io.Discard, nil))
5354
newIPTablesManager = func(conf *PluginConf) (iptables.Manager, error) {
54-
return iptables.NewIPTablesManager(conf.MainChainName, conf.DefaultAction, conf.DryRun)
55+
return iptables.NewIPTablesManager(conf.MainChainName, conf.DefaultAction, conf.DryRun, conf.LogDrops)
5556
}
5657
metadata = map[string]string{}
5758
)

pkg/iptables/iptables_manager.go

Lines changed: 59 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,10 @@ type IPTablesManager struct {
4646
mainChainName string
4747
defaultAction string
4848
dryRun bool
49+
logDrops bool
4950
}
5051

51-
func NewIPTablesManager(mainChainName, defaultAction string, dryRun bool) (Manager, error) {
52+
func NewIPTablesManager(mainChainName, defaultAction string, dryRun, logDrops bool) (Manager, error) {
5253
ipt, err := newIPTables()
5354
if err != nil {
5455
return nil, fmt.Errorf("failed to initialize iptables: %v", err)
@@ -67,6 +68,7 @@ func NewIPTablesManager(mainChainName, defaultAction string, dryRun bool) (Manag
6768
mainChainName: mainChainName,
6869
defaultAction: defaultAction,
6970
dryRun: dryRun,
71+
logDrops: logDrops,
7072
}, nil
7173
}
7274

@@ -107,7 +109,7 @@ func (m *IPTablesManager) CreateContainerChain(containerChain string) error {
107109
// In dry-run mode, add a logging rule for anything that reaches the default action
108110
logRuleSpec := []string{
109111
"-j", "LOG",
110-
"--log-prefix", fmt.Sprintf("[CNI-OUTBOUND-DEFAULT-%s] ", m.defaultAction),
112+
"--log-prefix", fmt.Sprintf(`"[CNI-OUTBOUND-%s-%s]"`, containerChain, m.defaultAction),
111113
}
112114
if err := m.ipt.Append("filter", containerChain, logRuleSpec...); err != nil {
113115
return fmt.Errorf("failed to add default action logging rule: %v", err)
@@ -127,36 +129,46 @@ func (m *IPTablesManager) CreateContainerChain(containerChain string) error {
127129
return nil
128130
}
129131

130-
func (m *IPTablesManager) AddRule(chainName string, rule OutboundRule) error {
131-
// Build basic rule specification
132-
ruleSpec := []string{"-d", rule.Host, "-p", rule.Proto, "--dport", rule.Port}
132+
// buildRuleSpecs returns one or more rules to insert into the chain.
133+
// Each returned element is a slice of strings representing the iptables arguments.
134+
func (m *IPTablesManager) buildRuleSpecs(chainName, host, proto, port, action string) [][]string {
135+
// Base rule spec
136+
baseSpec := []string{"-d", host, "-p", proto, "--dport", port}
133137

138+
// If dry-run => Always log + then ACCEPT
134139
if m.dryRun {
135-
// Add logging rule with prefix based on original action
136-
logRuleSpec := append([]string{}, ruleSpec...)
137-
var logPrefix string
138-
if rule.Action == "DROP" {
139-
logPrefix = "[CNI-OUTBOUND-BLOCKED]"
140-
} else {
141-
logPrefix = "[CNI-OUTBOUND-ACCEPTED]"
140+
return [][]string{
141+
append(append([]string{}, baseSpec...), "-j", "LOG", "--log-prefix", fmt.Sprintf(`"[CNI-OUTBOUND-%s-ACCEPTED]"`, chainName)),
142+
append(append([]string{}, baseSpec...), "-j", "ACCEPT"),
142143
}
144+
}
143145

144-
logRuleSpec = append(logRuleSpec,
145-
"-j", "LOG",
146-
"--log-prefix", logPrefix)
147-
148-
if err := m.ipt.Insert("filter", chainName, 1, logRuleSpec...); err != nil {
149-
return fmt.Errorf("failed to add logging rule: %v", err)
146+
// Normal mode. If this is a drop and logDrops == true => log + then drop
147+
if m.logDrops && strings.EqualFold(action, "DROP") {
148+
return [][]string{
149+
append(append([]string{}, baseSpec...), "-j", "LOG", "--log-prefix", fmt.Sprintf(`"[CNI-OUTBOUND-%s-BLOCKED]"`, chainName)),
150+
append(append([]string{}, baseSpec...), "-j", "DROP"),
150151
}
152+
}
153+
154+
// Otherwise, just a single final rule: -j <action>
155+
return [][]string{
156+
append(append([]string{}, baseSpec...), "-j", action),
157+
}
158+
}
151159

152-
// In dry-run mode, always ACCEPT after logging
153-
ruleSpec = append(ruleSpec, "-j", "ACCEPT")
154-
} else {
155-
// Normal mode - use the specified action
156-
ruleSpec = append(ruleSpec, "-j", rule.Action)
160+
func (m *IPTablesManager) AddRule(chainName string, rule OutboundRule) error {
161+
ruleSpecs := m.buildRuleSpecs(chainName, rule.Host, rule.Proto, rule.Port, rule.Action)
162+
163+
// Add rules in reverse order so they end up in the correct order
164+
// (since we're using Insert at position 1 each time)
165+
for i := len(ruleSpecs) - 1; i >= 0; i-- {
166+
if err := m.ipt.Insert("filter", chainName, 1, ruleSpecs[i]...); err != nil {
167+
return fmt.Errorf("failed to add rule: %v", err)
168+
}
157169
}
158170

159-
return m.ipt.Insert("filter", chainName, 1, ruleSpec...)
171+
return nil
160172
}
161173

162174
func (m *IPTablesManager) AddJumpRule(sourceIP, targetChain string) error {
@@ -185,63 +197,51 @@ func (m *IPTablesManager) ChainExists(chainName string) (bool, error) {
185197
return m.ipt.ChainExists("filter", chainName)
186198
}
187199

200+
// buildExpectedRuleLines constructs the strings we'll search for in `iptables -S <chain>` output.
201+
func (m *IPTablesManager) buildExpectedRuleLines(chainName string, host, proto, port, action string) []string {
202+
var lines []string
203+
ruleSets := m.buildRuleSpecs(chainName, host, proto, port, action)
204+
205+
// iptables -S lines typically look like:
206+
// -A <chainName> -d <host> -p <proto> --dport <port> -j <ACTION> ...
207+
// We'll create lines that we can search with strings.Contains().
208+
for _, rs := range ruleSets {
209+
// Start with `-A chainName` then the rest:
210+
line := "-A " + chainName + " " + strings.Join(rs, " ")
211+
lines = append(lines, line)
212+
}
213+
return lines
214+
}
215+
188216
func (m *IPTablesManager) VerifyRules(chainName string, rules []OutboundRule) error {
189217
existingRules, err := m.ipt.List("filter", chainName)
190218
if err != nil {
191219
return err
192220
}
193221

222+
// Verify each OutboundRule
194223
for _, rule := range rules {
195-
ruleSpec := fmt.Sprintf("-A %s -d %s -p %s --dport %s", chainName, rule.Host, rule.Proto, rule.Port)
196-
197-
if m.dryRun {
198-
// Check for logging rule
199-
logRuleSpec := ruleSpec + " -j LOG"
200-
found := false
201-
for _, existingRule := range existingRules {
202-
if strings.Contains(existingRule, logRuleSpec) {
203-
found = true
204-
break
205-
}
206-
}
207-
if !found {
208-
return fmt.Errorf("logging rule not found: %s", logRuleSpec)
209-
}
210-
211-
// Check for ACCEPT rule
212-
acceptRuleSpec := ruleSpec + " -j ACCEPT"
213-
found = false
214-
for _, existingRule := range existingRules {
215-
if strings.Contains(existingRule, acceptRuleSpec) {
216-
found = true
217-
break
218-
}
219-
}
220-
if !found {
221-
return fmt.Errorf("ACCEPT rule not found: %s", acceptRuleSpec)
222-
}
223-
} else {
224-
// Original rule verification
225-
ruleSpec = ruleSpec + fmt.Sprintf(" -j %s", rule.Action)
224+
expectedLines := m.buildExpectedRuleLines(chainName, rule.Host, rule.Proto, rule.Port, rule.Action)
225+
for _, expectedLine := range expectedLines {
226226
found := false
227227
for _, existingRule := range existingRules {
228-
if strings.Contains(existingRule, ruleSpec) {
228+
if strings.Contains(existingRule, expectedLine) {
229229
found = true
230230
break
231231
}
232232
}
233233
if !found {
234-
return fmt.Errorf("rule not found: %s", ruleSpec)
234+
return fmt.Errorf("rule not found: %s", expectedLine)
235235
}
236236
}
237237
}
238238

239239
// Verify default action logging rule in dry-run mode
240240
if m.dryRun {
241-
logPrefix := fmt.Sprintf("[CNI-OUTBOUND-DEFAULT-%s]", m.defaultAction)
241+
defaultLogLine := fmt.Sprintf("-A %s -j LOG --log-prefix [CNI-OUTBOUND-DEFAULT-%s]", chainName, m.defaultAction)
242242
found := false
243243
for _, existingRule := range existingRules {
244-
if strings.Contains(existingRule, "-j LOG") && strings.Contains(existingRule, logPrefix) {
244+
if strings.Contains(existingRule, defaultLogLine) {
245245
found = true
246246
break
247247
}

0 commit comments

Comments
 (0)