// Copyright 2024 Matthew Rich . All rights reserved. package resource import ( "context" _ "encoding/hex" "encoding/json" "errors" "fmt" "gopkg.in/yaml.v3" "io" "net/url" _ "os/exec" "regexp" "strconv" "strings" "log/slog" "gitea.rosskeen.house/rosskeen.house/machine" "decl/internal/codec" "decl/internal/command" "decl/internal/data" "decl/internal/folio" ) const ( IptableTypeName TypeName = "iptable" ) func init() { folio.DocumentRegistry.ResourceTypes.Register([]string{"iptable"}, func(u *url.URL) data.Resource { i := NewIptable() i.Table = IptableName(u.Hostname()) if len(u.Path) > 0 { fields := strings.FieldsFunc(u.Path, func(c rune) bool { return c == '/' }) slog.Info("iptables factory", "iptable", i, "uri", u, "fields", fields, "number_fields", len(fields)) if len(fields) > 0 { i.Chain = IptableChain(fields[0]) if len(fields) < 3 { i.ResourceType = IptableTypeChain } else { i.ResourceType = IptableTypeRule id, _ := strconv.ParseUint(fields[1], 10, 32) i.SetId(uint(id)) } } i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD() } return i }) } type IptableIPVersion string const ( IptableIPv4 IptableIPVersion = "ipv4" IPtableIPv6 IptableIPVersion = "ipv6" ) type IptableName string const ( IptableNameFilter IptableName = "filter" IptableNameNat IptableName = "nat" IptableNameMangel IptableName = "mangle" IptableNameRaw IptableName = "raw" IptableNameSecurity IptableName = "security" ) var IptableNumber = regexp.MustCompile(`^[0-9]+$`) type IptableChain string const ( IptableChainInput = "INPUT" IptableChainOutput = "OUTPUT" IptableChainForward = "FORWARD" IptableChainPreRouting = "PREROUTING" IptableChainPostRouting = "POSTROUTING" ) type IptableProto string const ( IptableProtoTCP = "tcp" IptableProtoUDP = "udp" IptableProtoUDPLite = "udplite" IptableProtoICMP = "icmp" IptableProtoICMPv6 = "icmpv6" IptableProtoESP = "ESP" IptableProtoAH = "AH" IptableProtoSCTP = "sctp" IptableProtoMH = "mh" IptableProtoAll = "all" ) type IptableCIDR string type ExtensionFlag struct { Name string `json:"name" yaml:"name"` Value string `json:"value" yaml:"value"` } type TargetFlag ExtensionFlag type IptablePort uint16 type IptableType string const ( IptableTypeRule = "rule" IptableTypeChain = "chain" ) type IptableRule string var ( ErrInvalidIptableName error = errors.New("The IptableName is not a valid table") ) // Manage the state of iptables rules // iptable://filter/INPUT/0 type Iptable struct { *Common `json:",inline" yaml:",inline"` stater machine.Stater `json:"-" yaml:"-"` Id uint `json:"id,omitempty" yaml:"id,omitempty"` Table IptableName `json:"table" yaml:"table"` Chain IptableChain `json:"chain" yaml:"chain"` Destination IptableCIDR `json:"destination,omitempty" yaml:"destination,omitempty"` Source IptableCIDR `json:"source,omitempty" yaml:"source,omitempty"` Dport IptablePort `json:"dport,omitempty" yaml:"dport,omitempty"` Sport IptablePort `json:"sport,omitempty" yaml:"sport,omitempty"` In string `json:"in,omitempty" yaml:"in,omitempty"` Out string `json:"out,omitempty" yaml:"out,omitempty"` Match []string `json:"match,omitempty" yaml:"match,omitempty"` Flags []ExtensionFlag `json:"extension_flags,omitempty" yaml:"extension_flags,omitempty"` Proto IptableProto `json:"proto,omitempty" yaml:"proto,omitempty"` Jump string `json:"jump,omitempty" yaml:"jump,omitempty"` TargetFlags []TargetFlag `json:"target_flags,omitempty" yaml:"target_flags,omitempty"` ChainLength uint `json:"-" yaml:"-"` ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"` CreateCommand *command.Command `yaml:"-" json:"-"` ReadCommand *command.Command `yaml:"-" json:"-"` ReadChainCommand *command.Command `yaml:"-" json:"-"` UpdateCommand *command.Command `yaml:"-" json:"-"` DeleteCommand *command.Command `yaml:"-" json:"-"` Resources data.ResourceMapper `yaml:"-" json:"-"` } func (n IptableName) Validate() error { switch n { case IptableNameFilter, IptableNameNat, IptableNameMangel, IptableNameRaw, IptableNameSecurity: return nil default: return ErrInvalidIptableName } } func NewIptable() (i *Iptable) { i = &Iptable{ ResourceType: IptableTypeRule } i.Common = NewCommon(IptableTypeName, false) i.Common.NormalizePath = i.NormalizePath i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD() i.ReadChainCommand = NewIptableChainReadCommand() return } func (i *Iptable) SetResourceMapper(resources data.ResourceMapper) { i.Resources = resources } func (i *Iptable) Clone() data.Resource { newIpt := &Iptable { Common: i.Common, Id: i.Id, Table: i.Table, Chain: i.Chain, Destination: i.Destination, Source: i.Source, In: i.In, Out: i.Out, Match: i.Match, Proto: i.Proto, ResourceType: i.ResourceType, } newIpt.CreateCommand, newIpt.ReadCommand, newIpt.UpdateCommand, newIpt.DeleteCommand = newIpt.ResourceType.NewCRUD() return newIpt } func (i *Iptable) StateMachine() machine.Stater { if i.stater == nil { i.stater = StorageMachine(i) } return i.stater } func (i *Iptable) Notify(m *machine.EventMessage) { ctx := context.Background() switch m.On { case machine.ENTERSTATEEVENT: switch m.Dest { case "start_stat": if statErr := i.ReadStat(ctx); statErr == nil { if triggerErr := i.StateMachine().Trigger("exists"); triggerErr == nil { return } } else { if triggerErr := i.StateMachine().Trigger("notexists"); triggerErr == nil { return } } case "start_read": if _,readErr := i.Read(ctx); readErr == nil { if triggerErr := i.stater.Trigger("state_read"); triggerErr == nil { return } else { i.Common.State = "absent" panic(triggerErr) } } else { i.Common.State = "absent" panic(readErr) } case "start_create": if createErr := i.Create(ctx); createErr == nil { if triggerErr := i.stater.Trigger("created"); triggerErr == nil { slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr) return } else { slog.Info("ContainerImage.Notify()", "created", i, "error", triggerErr) i.Common.State = "absent" panic(triggerErr) } } else { i.Common.State = "absent" panic(createErr) } case "start_update": if createErr := i.Update(ctx); createErr == nil { if triggerErr := i.stater.Trigger("updated"); triggerErr == nil { return } else { i.Common.State = "absent" } } else { i.Common.State = "absent" panic(createErr) } case "start_delete": if deleteErr := i.Delete(ctx); deleteErr == nil { if triggerErr := i.stater.Trigger("deleted"); triggerErr == nil { return } } else { panic(deleteErr) } case "present", "created", "read": i.Common.State = "present" case "absent": i.Common.State = "absent" } case machine.EXITSTATEEVENT: } } // Set the chain ID and update the mapped URI func (i *Iptable) SetId(id uint) { if i.Id != id { uri := i.URI() i.Id = id decl, ok := i.Resources.Get(uri) if ok { i.Resources.Delete(uri) } i.Resources.Set(i.URI(), decl) } } func (i *Iptable) URI() string { return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id) } func (i *Iptable) SetParsedURI(uri *url.URL) (err error) { if err = i.Common.SetParsedURI(uri); err == nil { err = i.setFieldsFromPath() } return } func (i *Iptable) NormalizePath() error { return nil } func (i *Iptable) setFieldsFromPath() (err error) { fields := strings.FieldsFunc(i.Common.Path, func(c rune) bool { return c == '/' }) fieldsLen := len(fields) if fieldsLen > 0 { i.Table = IptableName(fields[0]) if err = i.Table.Validate(); err != nil { return err } i.Chain = IptableChain(fields[1]) if fieldsLen < 3 { i.ResourceType = IptableTypeChain } else { i.ResourceType = IptableTypeRule id, _ := strconv.ParseUint(fields[1], 10, 32) i.Id = uint(id) } } else { err = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, i.Common.Uri) } return } func (i *Iptable) SetURI(uri string) (err error) { if err = i.Common.SetURI(uri); err == nil { err = i.setFieldsFromPath() } return } func (i *Iptable) Validate() error { s := NewSchema(i.Type()) jsonDoc, jsonErr := i.JSON() if jsonErr == nil { return s.Validate(string(jsonDoc)) } return jsonErr } func (i *Iptable) JSON() ([]byte, error) { return json.Marshal(i) } func (i *Iptable) UnmarshalJSON(data []byte) error { if unmarshalErr := json.Unmarshal(data, i); unmarshalErr != nil { return unmarshalErr } i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD() return nil } func (i *Iptable) UnmarshalYAML(value *yaml.Node) error { type decodeIptable Iptable if unmarshalErr := value.Decode((*decodeIptable)(i)); unmarshalErr != nil { return unmarshalErr } i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD() return nil } func (i *Iptable) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) { return NewIptableCreateCommand(), NewIptableReadCommand(), NewIptableUpdateCommand(), NewIptableDeleteCommand() } func (i *Iptable) Apply() error { ctx := context.Background() switch i.Common.State { case "absent": case "present": err := i.Create(ctx) if err != nil { return err } } _,e := i.Read(context.Background()) return e } func (i *Iptable) Load(docData []byte, f codec.Format) (err error) { err = f.StringDecoder(string(docData)).Decode(i) return } func (i *Iptable) LoadReader(r io.ReadCloser, f codec.Format) (err error) { err = f.Decoder(r).Decode(i) return } func (i *Iptable) LoadString(docData string, f codec.Format) (err error) { err = f.StringDecoder(docData).Decode(i) return } func (i *Iptable) LoadDecl(yamlResourceDeclaration string) error { return i.LoadString(yamlResourceDeclaration, codec.FormatYaml) } func (i *Iptable) ResolveId(ctx context.Context) string { // uri := fmt.Sprintf("%s?gateway=%s&interface=%s&rtid=%s&metric=%d&type=%s&scope=%s", // n.To, n.Gateway, n.Interface, n.Rtid, n.Metric, n.RouteType, n.Scope) // n.Id = hex.EncodeToString([]byte(uri)) return fmt.Sprintf("%d", i.Id) } func (f *ExtensionFlag) Match(name string, value string) bool { start := 0 if name[1] == '-' { start = 2 } else if name[0] == '-' { start = 1 } return f.Name == name[start:] && f.Value == value } func (i *Iptable) HasExtensionFlag(name string) bool { optName := strings.Trim(name, "-") for _, ext := range i.Flags { if ext.Name == optName { return true } } return false } func (i *Iptable) HasTargetFlag(name string) bool { optName := strings.Trim(name, "-") for _, ext := range i.TargetFlags { if ext.Name == optName { return true } } return false } func (f *TargetFlag) Match(name string, value string) bool { start := 0 if name[1] == '-' { start = 2 } else if name[0] == '-' { start = 1 } return f.Name == name[start:] && f.Value == value } func (i *Iptable) SetFlagValue(opt, value string) bool { switch opt { case "-i": i.In = value return true case "-o": i.Out = value return true case "-m": for _,search := range i.Match { if search == value { return true } } i.Match = append(i.Match, value) return true case "-s": i.Source = IptableCIDR(value) return true case "-d": i.Destination = IptableCIDR(value) return true case "-p": i.Proto = IptableProto(value) return true case "-j": i.Jump = value return true case "--dport": port,_ := strconv.ParseUint(value, 10, 16) i.Dport = IptablePort(port) return true case "--sport": port,_ := strconv.ParseUint(value, 10, 16) i.Sport = IptablePort(port) return true default: if opt[0] == '-' { if len(i.Jump) > 0 { i.TargetFlags = append(i.TargetFlags, TargetFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)}) } else { i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(value)}) } return true } } return false } func (i *Iptable) GetFlagValue(opt string) any { switch opt { case "-i": return i.In case "-o": return i.Out case "-m": return i.Match case "-s": return i.Source case "-d": return i.Destination case "-p": return i.Proto case "-j": return i.Jump case "--dport": return strconv.Itoa(int(i.Dport)) case "--sport": return strconv.Itoa(int(i.Sport)) default: if opt[0] == '-' { slog.Info("Iptable.GetFlagValue()", "opt", opt, "iptable", i) if i.HasExtensionFlag(opt) { return i.Flags } if i.HasTargetFlag(opt) { return i.TargetFlags } } } return nil } func (i *Iptable) SetRule(flags []string) (assigned bool) { assigned = true for index, flag := range flags { if flag[0] == '-' { flag := flags[index] value := flags[index + 1] if value[0] != '-' { if ! i.SetFlagValue(flag, value) { assigned = false } } } } return } func (i *Iptable) MatchRule(flags []string) (match bool) { match = true next: for index, flag := range flags { if flag[0] == '-' { value := flags[index + 1] slog.Info("Iptable.MatchRule()", "flag", flag, "value", value) switch v := i.GetFlagValue(flag).(type) { case []string: for _,element := range v { if element == value { continue } } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false case []ExtensionFlag: for _,element := range v { if element.Match(flag, value) { continue next } } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false case []TargetFlag: for _,element := range v { if element.Match(flag, value) { continue next } } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false case IptableCIDR: if v == IptableCIDR(value) { continue } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false case IptableName: if v == IptableName(value) { continue } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false case IptableChain: if v == IptableChain(value) { continue } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false default: if v.(string) == value { continue } slog.Info("Iptable.MatchRule() - FAILED", "flag", flag, "value", v) match = false } } } slog.Info("Iptable.MatchRule()", "flags", flags, "match", match) return } func (i *Iptable) ReadChainLength() error { output,err := i.ReadChainCommand.Execute(i) if err == nil { linesCount := strings.Count(string(output), "\n") if linesCount > 0 { i.ChainLength = uint(linesCount) - 1 } else { i.ChainLength = 0 } } return err } func (i *Iptable) Create(ctx context.Context) (err error) { slog.Info("Iptable.Create()", "iptable", i) if i.Id > 0 { if lenErr := i.ReadChainLength(); lenErr != nil { return lenErr } } _, err = i.CreateCommand.Execute(i) slog.Info("Iptable.Create()", "err", err, "iptable", i, "createcommand", i.CreateCommand) //slog.Info("IptableChain Create()", "err", err, "errstr", err.Error(), "iptable", i, "createcommand", i.CreateCommand) // TODO add Command status/error handler rather than using the read extractor if i.CreateCommand.Extractor != nil { if err != nil { return i.CreateCommand.Extractor([]byte(err.Error()), i) } } return err } func (i *Iptable) ReadStat(ctx context.Context) (err error) { if i.ReadCommand.Exists() { var out []byte if out, err = i.ReadCommand.Execute(i); err == nil { err = i.ReadCommand.Extractor(out, i) } } if i.Id == 0 { return ErrResourceStateAbsent } return err } func (i *Iptable) Read(ctx context.Context) ([]byte, error) { out, err := i.ReadCommand.Execute(i) if err != nil { return nil, err } exErr := i.ReadCommand.Extractor(out, i) if exErr != nil { return nil, exErr } return yaml.Marshal(i) } func (i *Iptable) Update(ctx context.Context) error { return i.Create(ctx) } func (i *Iptable) Delete(ctx context.Context) (err error) { if i.Id < 1 { return fmt.Errorf("Failed to find rule to delete") } var out []byte if out, err = i.DeleteCommand.Execute(i); err == nil { err = i.DeleteCommand.Extractor(out, i) } return } func (i *Iptable) Type() string { return "iptable" } func (i *IptableType) NewCRUD() (create *command.Command, read *command.Command, update *command.Command, del *command.Command) { switch *i { case IptableTypeRule: return NewIptableCreateCommand(), NewIptableReadCommand(), NewIptableUpdateCommand(), NewIptableDeleteCommand() case IptableTypeChain: return NewIptableChainCreateCommand(), NewIptableChainReadCommand(), NewIptableChainUpdateCommand(), NewIptableChainDeleteCommand() default: } return nil, nil, nil, nil } func (i *IptableType) UnmarshalValue(value string) error { switch value { case string(IptableTypeRule), string(IptableTypeChain): *i = IptableType(value) return nil default: return errors.New("invalid IptableType value") } } func (i *IptableType) UnmarshalJSON(data []byte) error { var s string if unmarshalRouteTypeErr := json.Unmarshal(data, &s); unmarshalRouteTypeErr != nil { return unmarshalRouteTypeErr } return i.UnmarshalValue(s) } func (i *IptableType) UnmarshalYAML(value *yaml.Node) error { var s string if err := value.Decode(&s); err != nil { return err } return i.UnmarshalValue(s) } func NewIptableCreateCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("{{ if and (le .Id .ChainLength) (gt .Id 0) }}-R {{ .Chain }} {{ .Id }}{{ else }}-A {{ .Chain }}{{ end }}"), command.CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"), command.CommandArg("{{ range .Match }}-m {{ . }} {{- end }}"), command.CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"), command.CommandArg("{{ if .Sport }}--sport {{ .Sport }}{{ end }}"), command.CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"), command.CommandArg("{{ if .Dport }}--dport {{ .Dport }}{{ end }}"), command.CommandArg("{{ if .Proto }}-p {{ .Proto }}{{ end }}"), command.CommandArg("{{ range .Flags -}} --{{ .Name }} {{ .Value }} {{- end }}"), command.CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"), command.CommandArg("{{ range .TargetFlags -}} --{{ .Name }} {{ .Value }}{{- end }}"), } return c } func IptableExtractRule(lineNumber uint, ruleLine string, target *Iptable) (state string, err error) { state = "absent" ruleFields := strings.Split(strings.TrimSpace(ruleLine), " ") slog.Info("IptableExtractRule()", "lineNumber", lineNumber, "ruleLine", ruleLine, "target", target) if ruleFields[0] == "-A" { flags := ruleFields[2:] if target.Id > 0 { if target.Id == lineNumber { slog.Info("IptableExtractRule() SetRule", "lineNumber", lineNumber, "flags", flags, "target", target) if target.SetRule(flags) { state = "present" err = nil } } } else { if target.MatchRule(flags) { target.SetId(lineNumber) state = "present" err = nil } } } else { err = fmt.Errorf("Invalid rule %d %s", lineNumber, ruleLine) } return } func NewIptableReadCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-S"), command.CommandArg("{{ .Chain }}"), command.CommandArg("{{ if .Id }}{{ .Id }}{{ end }}"), } c.Extractor = func(out []byte, target any) error { i := target.(*Iptable) if i.Id > 0 { return RuleExtractor(out, target) } state := "absent" var lineNumber uint = 1 lines := strings.Split(string(out), "\n") numberOfLines := len(lines) for _, line := range lines { matchState, err := IptableExtractRule(lineNumber, line, i) if matchState == "present" { state = matchState break } if err == nil { lineNumber++ } } i.Common.State = state if numberOfLines > 0 { i.ChainLength = uint(numberOfLines) - 1 } else { i.ChainLength = 0 } return nil } return c } func NewIptableReadChainCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-S"), command.CommandArg("{{ .Chain }}"), } c.Extractor = func(out []byte, target any) error { IptableChainRules := target.(*[]*Iptable) numberOfChainRules := len(*IptableChainRules) lines := strings.Split(strings.TrimSpace(string(out)), "\n") numberOfLines := len(lines) diff := (numberOfLines - 1) - numberOfChainRules if diff > 0 { for i := 0; i < diff; i++ { *IptableChainRules = append(*IptableChainRules, NewIptable()) } } for lineIndex, line := range lines[1:] { i := (*IptableChainRules)[lineIndex] i.SetId(uint(lineIndex + 1)) ruleFields := strings.Split(strings.TrimSpace(line), " ") if ruleFields[0] == "-A" { flags := ruleFields[2:] if i.SetRule(flags) { i.Common.State = "present" } else { i.Common.State = "absent" } } } return nil } return c } func NewIptableUpdateCommand() *command.Command { return NewIptableCreateCommand() } func NewIptableDeleteCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-D"), command.CommandArg("{{ .Chain }}"), command.CommandArg("{{ .Id }}"), } return c } func NewIptableChainCreateCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-N"), command.CommandArg("{{ .Chain }}"), } c.Extractor = func(out []byte, target any) error { slog.Info("IptableChain Extractor", "output", out, "command", c) for _,line := range strings.Split(string(out), "\n") { if line == "iptables: Chain already exists." { return nil } } return fmt.Errorf(string(out)) } return c } func ChainExtractor(out []byte, target any) error { i := target.(*Iptable) rules := strings.Split(string(out), "\n") for _,rule := range rules { ruleFields := strings.Split(strings.TrimSpace(string(rule)), " ") switch ruleFields[0] { case "-N", "-A": chain := ruleFields[1] if chain == string(i.Chain) { i.Common.State = "present" return nil } else { i.Common.State = "absent" } default: i.Common.State = "absent" } } return nil } func RuleExtractor(out []byte, target any) (err error) { ipt := target.(*Iptable) lines := strings.Split(strings.TrimSpace(string(out)), "\n") err = fmt.Errorf("Failed to extract rule by Id: %d", ipt.Id) ipt.Common.State = "absent" var lineIndex uint = 1 if uint(len(lines)) >= ipt.Id { lineIndex = ipt.Id } else if len(lines) > 2 { return } ruleFields := strings.Split(strings.TrimSpace(lines[lineIndex]), " ") slog.Info("RuleExtractor()", "lines", lines, "line", lines[lineIndex], "fields", ruleFields, "index", lineIndex) if ruleFields[0] == "-A" { if ipt.SetRule(ruleFields[2:]) { ipt.Common.State = "present" err = nil } } return } func RuleExtractorMatchFlags(out []byte, target any) (err error) { ipt := target.(*Iptable) lines := strings.Split(strings.TrimSpace(string(out)), "\n") var linesCount uint = uint(len(lines)) err = fmt.Errorf("Failed to extract rule") if linesCount > 0 { ipt.ChainLength = linesCount - 1 ipt.Common.State = "absent" for linesIndex, line := range lines { ruleFields := strings.Split(strings.TrimSpace(line), " ") slog.Info("RuleExtractorMatchFlags()", "lines", lines, "line", line, "fields", ruleFields, "index", linesIndex) if ruleFields[0] == "-A" { flags := ruleFields[2:] if ipt.MatchRule(flags) { slog.Info("RuleExtractorMatchFlags()", "flags", flags, "ipt", ipt) err = nil ipt.Common.State = "present" ipt.SetId(uint(linesIndex)) return } } } } return } func RuleExtractorById(out []byte, target any) (err error) { ipt := target.(*Iptable) state := "absent" lines := strings.Split(string(out), "\n") err = fmt.Errorf("Failed to extract rule by Id: %d", ipt.Id) ipt.ChainLength = 0 for _, line := range lines { ruleFields := strings.Split(strings.TrimSpace(line), " ") if ruleFields[0] == "-A" { ipt.ChainLength++ flags := ruleFields[2:] slog.Info("RuleExtractorById()", "target", ipt) if ipt.Id == ipt.ChainLength { if ipt.SetRule(flags) { slog.Info("RuleExtractorById() SetRule", "flags", flags, "target", ipt) state = "present" err = nil } } } } ipt.Common.State = state return } func NewIptableChainReadCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-S"), command.CommandArg("{{ .Chain }}"), } c.Extractor = func(out []byte, target any) error { i := target.(*Iptable) rules := strings.Split(string(out), "\n") for _,rule := range rules { ruleFields := strings.Split(strings.TrimSpace(string(rule)), " ") slog.Info("IptableChain Extract()", "fields", ruleFields) switch ruleFields[0] { case "-N", "-A": chain := ruleFields[1] if chain == string(i.Chain) { i.Common.State = "present" return nil } else { i.Common.State = "absent" } default: i.Common.State = "absent" } } return nil } return c } func NewIptableChainUpdateCommand() *command.Command { return NewIptableChainCreateCommand() } func NewIptableChainDeleteCommand() *command.Command { c := command.NewCommand() c.Path = "iptables" c.Args = []command.CommandArg{ command.CommandArg("-t"), command.CommandArg("{{ .Table }}"), command.CommandArg("-X"), command.CommandArg("{{ .Chain }}"), } return c }