jx/internal/resource/iptables.go
Matthew Rich 8feb7b8d56
Some checks failed
Lint / golangci-lint (push) Failing after 10m1s
Declarative Tests / test (push) Failing after 14s
add support of import search paths [doublejynx/jx#7]
2024-10-16 10:26:42 -07:00

1036 lines
26 KiB
Go

// Copyright 2024 Matthew Rich <matthewrich.conf@gmail.com>. 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()
if u != nil {
if err := folio.CastParsedURI(u).ConstructResource(i); err != nil {
panic(err)
}
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) Init(u data.URIParser) (err error) {
if u == nil {
u = folio.URI(i.URI()).Parse()
}
uri := u.URL()
err = i.SetParsedURI(u)
i.Table = IptableName(uri.Hostname())
if len(uri.Path) > 0 {
fields := strings.FieldsFunc(uri.Path, func(c rune) bool { return c == '/' })
slog.Info("iptables factory", "iptable", i, "uri", uri, "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
}
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 data.URIParser) (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) 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
}