package firewall import ( "context" "fmt" "os/exec" "strconv" "strings" ) // isDeleteMatchInstruction returns true if the iptables instruction // is a delete instruction by rule matching. It returns false if the // instruction is a delete instruction by line number, or not a delete // instruction. func isDeleteMatchInstruction(instruction string) bool { fields := strings.Fields(instruction) for i, field := range fields { switch { case field != "-D" && field != "--delete": //nolint:goconst continue case i == len(fields)-1: // malformed: missing chain name return false case i == len(fields)-2: // chain name is last field return true default: // chain name is fields[i+1] const base, bitLength = 10, 16 _, err := strconv.ParseUint(fields[i+2], base, bitLength) return err != nil // not a line number } } return false } func deleteIPTablesRule(ctx context.Context, iptablesBinary, instruction string, runner CmdRunner, logger Logger, ) (err error) { targetRule, err := parseIptablesInstruction(instruction) if err != nil { return fmt.Errorf("parsing iptables command: %w", err) } lineNumber, err := findLineNumber(ctx, iptablesBinary, targetRule, runner, logger) if err != nil { return fmt.Errorf("finding iptables chain rule line number: %w", err) } else if lineNumber == 0 { logger.Debug("rule matching \"" + instruction + "\" not found") return nil } logger.Debug(fmt.Sprintf("found iptables chain rule matching %q at line number %d", instruction, lineNumber)) cmd := exec.CommandContext(ctx, iptablesBinary, "-t", targetRule.table, "-D", targetRule.chain, fmt.Sprint(lineNumber)) // #nosec G204 logger.Debug(cmd.String()) output, err := runner.Run(cmd) if err != nil { err = fmt.Errorf("command failed: %q: %w", cmd, err) if output != "" { err = fmt.Errorf("%w: %s", err, output) } return err } return nil } // findLineNumber finds the line number of an iptables rule. // It returns 0 if the rule is not found. func findLineNumber(ctx context.Context, iptablesBinary string, instruction iptablesInstruction, runner CmdRunner, logger Logger) ( lineNumber uint16, err error, ) { listFlags := []string{ "-t", instruction.table, "-L", instruction.chain, "--line-numbers", "-n", "-v", } cmd := exec.CommandContext(ctx, iptablesBinary, listFlags...) // #nosec G204 logger.Debug(cmd.String()) output, err := runner.Run(cmd) if err != nil { err = fmt.Errorf("command failed: %q: %w", cmd, err) if output != "" { err = fmt.Errorf("%w: %s", err, output) } return 0, err } chain, err := parseChain(output) if err != nil { return 0, fmt.Errorf("parsing chain list: %w", err) } for _, rule := range chain.rules { if instruction.equalToRule(instruction.table, chain.name, rule) { return rule.lineNumber, nil } } return 0, nil }