go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/iptables.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources
     5  
     6  import (
     7  	"errors"
     8  	"fmt"
     9  	"io"
    10  	"net"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"go.mondoo.com/cnquery/llx"
    15  	"go.mondoo.com/cnquery/providers/os/connection/shared"
    16  )
    17  
    18  // Stat represents a structured statistic entry.
    19  type Stat struct {
    20  	LineNumber  int64
    21  	Packets     int64
    22  	Bytes       int64
    23  	Target      string
    24  	Protocol    string
    25  	Opt         string
    26  	Input       string
    27  	Output      string
    28  	Source      string
    29  	Destination string
    30  	Options     string
    31  }
    32  
    33  func (ie *mqlIptablesEntry) id() (string, error) {
    34  	return strconv.FormatInt(ie.LineNumber.Data, 10) + ie.Chain.Data, nil
    35  }
    36  
    37  func (i *mqlIptables) output() ([]interface{}, error) {
    38  	conn := i.MqlRuntime.Connection.(shared.Connection)
    39  
    40  	ipstats := []interface{}{}
    41  	cmd, err := conn.RunCommand("iptables -L OUTPUT -v -n -x --line-numbers")
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  	data, err := io.ReadAll(cmd.Stdout)
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  	if cmd.ExitStatus != 0 {
    50  		outErr, _ := io.ReadAll(cmd.Stderr)
    51  		return nil, errors.New(string(outErr))
    52  	}
    53  	lines := getLines(string(data))
    54  	stats, err := ParseStat(lines, false)
    55  	if err != nil {
    56  		return nil, err
    57  	}
    58  	for _, stat := range stats {
    59  		entry, err := CreateResource(i.MqlRuntime, "iptables.entry", map[string]*llx.RawData{
    60  			"lineNumber":  llx.IntData(stat.LineNumber),
    61  			"packets":     llx.IntData(stat.Packets),
    62  			"bytes":       llx.IntData(stat.Bytes),
    63  			"target":      llx.StringData(stat.Target),
    64  			"protocol":    llx.StringData(stat.Protocol),
    65  			"opt":         llx.StringData(stat.Opt),
    66  			"in":          llx.StringData(stat.Input),
    67  			"out":         llx.StringData(stat.Output),
    68  			"source":      llx.StringData(stat.Source),
    69  			"destination": llx.StringData(stat.Destination),
    70  			"options":     llx.StringData(stat.Options),
    71  			"chain":       llx.StringData("output"),
    72  		})
    73  		if err != nil {
    74  			return nil, err
    75  		}
    76  		ipstats = append(ipstats, entry.(*mqlIptablesEntry))
    77  	}
    78  	return ipstats, nil
    79  }
    80  
    81  func (i *mqlIptables) input() ([]interface{}, error) {
    82  	conn := i.MqlRuntime.Connection.(shared.Connection)
    83  
    84  	ipstats := []interface{}{}
    85  	cmd, err := conn.RunCommand("iptables -L INPUT -v -n -x --line-numbers")
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	data, err := io.ReadAll(cmd.Stdout)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  	if cmd.ExitStatus != 0 {
    94  		outErr, _ := io.ReadAll(cmd.Stderr)
    95  		return nil, errors.New(string(outErr))
    96  	}
    97  	lines := getLines(string(data))
    98  	stats, err := ParseStat(lines, false)
    99  	if err != nil {
   100  		return nil, err
   101  	}
   102  	for _, stat := range stats {
   103  		entry, err := CreateResource(i.MqlRuntime, "iptables.entry", map[string]*llx.RawData{
   104  			"lineNumber":  llx.IntData(stat.LineNumber),
   105  			"packets":     llx.IntData(stat.Packets),
   106  			"bytes":       llx.IntData(stat.Bytes),
   107  			"target":      llx.StringData(stat.Target),
   108  			"protocol":    llx.StringData(stat.Protocol),
   109  			"opt":         llx.StringData(stat.Opt),
   110  			"in":          llx.StringData(stat.Input),
   111  			"out":         llx.StringData(stat.Output),
   112  			"source":      llx.StringData(stat.Source),
   113  			"destination": llx.StringData(stat.Destination),
   114  			"options":     llx.StringData(stat.Options),
   115  			"chain":       llx.StringData("input"),
   116  		})
   117  		if err != nil {
   118  			return nil, err
   119  		}
   120  		ipstats = append(ipstats, entry.(*mqlIptablesEntry))
   121  	}
   122  	return ipstats, nil
   123  }
   124  
   125  func (i *mqlIp6tables) output() ([]interface{}, error) {
   126  	conn := i.MqlRuntime.Connection.(shared.Connection)
   127  
   128  	ipstats := []interface{}{}
   129  	cmd, err := conn.RunCommand("ip6tables -L OUTPUT -v -n -x --line-numbers")
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	data, err := io.ReadAll(cmd.Stdout)
   134  	if err != nil {
   135  		return nil, err
   136  	}
   137  	if cmd.ExitStatus != 0 {
   138  		outErr, _ := io.ReadAll(cmd.Stderr)
   139  		return nil, errors.New(string(outErr))
   140  	}
   141  	lines := getLines(string(data))
   142  	stats, err := ParseStat(lines, true)
   143  	if err != nil {
   144  		return nil, err
   145  	}
   146  	for _, stat := range stats {
   147  		entry, err := CreateResource(i.MqlRuntime, "iptables.entry", map[string]*llx.RawData{
   148  			"lineNumber":  llx.IntData(stat.LineNumber),
   149  			"packets":     llx.IntData(stat.Packets),
   150  			"bytes":       llx.IntData(stat.Bytes),
   151  			"target":      llx.StringData(stat.Target),
   152  			"protocol":    llx.StringData(stat.Protocol),
   153  			"opt":         llx.StringData(stat.Opt),
   154  			"in":          llx.StringData(stat.Input),
   155  			"out":         llx.StringData(stat.Output),
   156  			"source":      llx.StringData(stat.Source),
   157  			"destination": llx.StringData(stat.Destination),
   158  			"options":     llx.StringData(stat.Options),
   159  			"chain":       llx.StringData("output6"),
   160  		})
   161  		if err != nil {
   162  			return nil, err
   163  		}
   164  		ipstats = append(ipstats, entry.(*mqlIptablesEntry))
   165  	}
   166  	return ipstats, nil
   167  }
   168  
   169  func (i *mqlIp6tables) input() ([]interface{}, error) {
   170  	conn := i.MqlRuntime.Connection.(shared.Connection)
   171  
   172  	ipstats := []interface{}{}
   173  	cmd, err := conn.RunCommand("ip6tables -L INPUT -v -n -x --line-numbers")
   174  	if err != nil {
   175  		return nil, err
   176  	}
   177  	data, err := io.ReadAll(cmd.Stdout)
   178  	if err != nil {
   179  		return nil, err
   180  	}
   181  
   182  	if cmd.ExitStatus != 0 {
   183  		outErr, _ := io.ReadAll(cmd.Stderr)
   184  		return nil, errors.New(string(outErr))
   185  	}
   186  	lines := getLines(string(data))
   187  	stats, err := ParseStat(lines, true)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	for _, stat := range stats {
   192  		entry, err := CreateResource(i.MqlRuntime, "iptables.entry", map[string]*llx.RawData{
   193  			"lineNumber":  llx.IntData(stat.LineNumber),
   194  			"packets":     llx.IntData(stat.Packets),
   195  			"bytes":       llx.IntData(stat.Bytes),
   196  			"target":      llx.StringData(stat.Target),
   197  			"protocol":    llx.StringData(stat.Protocol),
   198  			"opt":         llx.StringData(stat.Opt),
   199  			"in":          llx.StringData(stat.Input),
   200  			"out":         llx.StringData(stat.Output),
   201  			"source":      llx.StringData(stat.Source),
   202  			"destination": llx.StringData(stat.Destination),
   203  			"options":     llx.StringData(stat.Options),
   204  			"chain":       llx.StringData("input6"),
   205  		})
   206  		if err != nil {
   207  			return nil, err
   208  		}
   209  		ipstats = append(ipstats, entry.(*mqlIptablesEntry))
   210  	}
   211  	return ipstats, nil
   212  }
   213  
   214  // Credit to github.com/coreos/go-iptables for some of the parsing logic
   215  func getLines(data string) []string {
   216  	rules := strings.Split(data, "\n")
   217  
   218  	// strip trailing newline
   219  	if len(rules) > 0 && rules[len(rules)-1] == "" {
   220  		rules = rules[:len(rules)-1]
   221  	}
   222  
   223  	return rules
   224  }
   225  
   226  func ParseStat(lines []string, ipv6 bool) ([]Stat, error) {
   227  	entries := []Stat{}
   228  	for i, line := range lines {
   229  		// Skip over chain name and field header
   230  		if i < 2 {
   231  			continue
   232  		}
   233  
   234  		// Fields:
   235  		// 0=linenumber 1=pkts 2=bytes 3=target 4=prot 5=opt 6=in 7=out 8=source 9=destination 10=options
   236  		line = strings.TrimSpace(line)
   237  		fields := strings.Fields(line)
   238  
   239  		// The ip6tables verbose output cannot be naively split due to the default "opt"
   240  		// field containing 2 single spaces.
   241  		if ipv6 {
   242  			// Check if field 7 is "out" or "source" address
   243  			dest := fields[7]
   244  			ip, _, _ := net.ParseCIDR(dest)
   245  			if ip == nil {
   246  				ip = net.ParseIP(dest)
   247  			}
   248  
   249  			// If we detected a CIDR or IP, the "opt" field is empty.. insert it.
   250  			if ip != nil {
   251  				f := []string{}
   252  				f = append(f, fields[:5]...)
   253  				f = append(f, "  ") // Empty "opt" field for ip6tables
   254  				f = append(f, fields[5:]...)
   255  				fields = f
   256  			}
   257  		}
   258  		ln, err := strconv.ParseInt(fields[0], 0, 64)
   259  		if err != nil {
   260  			return entries, fmt.Errorf(err.Error(), "could not parse line number")
   261  		}
   262  		pkts, err := strconv.ParseInt(fields[1], 0, 64)
   263  		if err != nil {
   264  			return entries, fmt.Errorf(err.Error(), "could not parse packets")
   265  		}
   266  		bts, err := strconv.ParseInt(fields[2], 0, 64)
   267  		if err != nil {
   268  			return entries, fmt.Errorf(err.Error(), "could not parse bytes")
   269  		}
   270  		var opts string
   271  		// combine options if they exist
   272  		if len(fields) > 10 {
   273  			o := fields[10:]
   274  			opts = strings.Join(o, " ")
   275  		}
   276  		entry := Stat{
   277  			LineNumber:  ln,
   278  			Packets:     pkts,
   279  			Bytes:       bts,
   280  			Target:      fields[3],
   281  			Protocol:    fields[4],
   282  			Opt:         fields[5],
   283  			Input:       fields[6],
   284  			Output:      fields[7],
   285  			Source:      fields[8],
   286  			Destination: fields[9],
   287  			Options:     opts,
   288  		}
   289  		entries = append(entries, entry)
   290  	}
   291  	return entries, nil
   292  }