1 package nodefirewall
2
3 import (
4 "errors"
5 "fmt"
6 "path/filepath"
7 "strings"
8
9 "slices"
10
11 "github.com/spf13/afero"
12
13 v1ien "edge-infra.dev/pkg/sds/ien/k8s/apis/v1"
14 )
15
16 const (
17
18 ruleTemplate = "%s-p %[2]s -m %[2]s --dport %s %s%s-j %s"
19 fwDirectory = "/etc/ien-fw/ipv4/dynamic"
20 inputFilename = "filter-INPUT-%s.rules"
21 outputFilename = "filter-OUTPUT-%s.rules"
22 fileHeaderComment = "# ClusterFirewall: %s, NodeFirewall: %s, Rule: %s"
23 )
24
25 var (
26 actions = map[v1ien.Action]string{
27 v1ien.Allow: "ACCEPT",
28 v1ien.Deny: "LOGDROP",
29 }
30 directionalInterfaceFlags = map[v1ien.Direction]string{
31 v1ien.Input: "-i %s ",
32 v1ien.Output: "-o %s ",
33 }
34 )
35
36 func (fw Plugin) writeFiles(rules []v1ien.NodeRule) ([]string, error) {
37 fs := fw.config.Fs()
38 files := []string{}
39
40 for i := range rules {
41 filename, err := fw.writeFile(fs, &rules[i])
42 if err != nil {
43 return files, err
44 }
45 files = append(files, filename)
46 }
47 return files, nil
48 }
49
50 func (fw Plugin) writeFile(fs afero.Fs, rule *v1ien.NodeRule) (string, error) {
51 filename, bytes := fw.generateRuleFile(rule)
52 if err := afero.WriteFile(fs, filename, bytes, 0644); err != nil {
53 return "", err
54 }
55
56 return filename, nil
57 }
58
59 func (fw Plugin) generateRuleFile(rule *v1ien.NodeRule) (string, []byte) {
60
61
62 lines := []string{}
63
64 for i := range rule.Filters {
65 lines = append(lines, fw.generateRuleFromFilter(rule, &rule.Filters[i]))
66 }
67 lines = append(lines, "")
68
69 filename := filepath.Join(fwDirectory, fw.generateFilename(rule))
70 content := []byte(strings.Join(lines, "\n"))
71
72 return filename, content
73 }
74
75 func (fw Plugin) generateRuleFromFilter(rule *v1ien.NodeRule, filter *v1ien.Filter) string {
76
77 sourceRanges := strings.Join(rule.SourceRanges, ",")
78 destinationRanges := strings.Join(rule.DestinationRanges, ",")
79 if sourceRanges != "" {
80 sourceRanges = "-s " + sourceRanges + " "
81 }
82 if destinationRanges != "" {
83 destinationRanges = "-d " + destinationRanges + " "
84 }
85 ifaceFlag, err := fw.getInterfaceFlag(rule)
86 if err != nil {
87 return ""
88 }
89 return fmt.Sprintf(ruleTemplate, ifaceFlag, filter.IPProtocol, filter.PortRange, sourceRanges, destinationRanges, actions[filter.Action])
90 }
91
92 func (fw Plugin) getInterfaceFlag(rule *v1ien.NodeRule) (string, error) {
93 var iface string
94 var err error
95
96 if rule.InterfaceMAC == "" {
97 iface = fw.defaultInterface
98 } else {
99 iface, err = fw.findInterfaceByMAC(rule.InterfaceMAC)
100 if err != nil {
101 return "", err
102 }
103 }
104
105 return fmt.Sprintf(directionalInterfaceFlags[rule.Direction], iface), nil
106 }
107
108 func (fw Plugin) deleteFiles(files []string) error {
109 fs := fw.config.Fs()
110 for _, file := range files {
111 if err := fs.Remove(file); err != nil && !errors.Is(err, afero.ErrFileNotFound) {
112 return err
113 }
114 }
115 return nil
116 }
117
118 func (fw Plugin) deleteStaleFiles(old, new []string) error {
119 fs := fw.config.Fs()
120 for _, file := range old {
121 if !slices.Contains(new, file) {
122 if err := fs.Remove(file); err != nil && !errors.Is(err, afero.ErrFileNotFound) {
123 return err
124 }
125 }
126 }
127 return nil
128 }
129
130 func (fw Plugin) generateFilename(rule *v1ien.NodeRule) string {
131 var filename string
132 if rule.Direction == v1ien.Input {
133 filename = inputFilename
134 } else {
135 filename = outputFilename
136 }
137 featureName := firstN(rule.ID, 8)
138 return fmt.Sprintf(filename, featureName)
139 }
140
141
142 func firstN(str string, n int) string {
143 if len(str) < n {
144 return str
145 }
146 return str[:n]
147 }
148
149 func (fw Plugin) findInterfaceByMAC(macAddress string) (string, error) {
150 netlink, err := fw.config.GetInterfaceFromHardwareAddress(macAddress)
151 if err != nil {
152 return "", err
153 }
154 return netlink.Attrs().Name, nil
155 }
156
View as plain text