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 }