...

Source file src/edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/nodefirewall/firewall_rules.go

Documentation: edge-infra.dev/pkg/sds/ien/k8s/controllers/nodeagent/plugins/nodefirewall

     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  	// Example rule: `-i eno1 -p udp -m udp --dport 67:68 -s 172.23.1.1 -d 172.23.1.5 -j ACCEPT`
    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  	// add this back in for edge-os v1.11.0
    61  	// lines := []string{fmt.Sprintf(fileHeaderComment, fw.owner, fw.name, rule.Name)}
    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  	// rule v1ien.NodeRule, filter v1ien.Filter, action string
    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 "" // don't add rule if no interface matches specified MAC
    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  // take the first n chars of a string
   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