844 lines
21 KiB
Go
844 lines
21 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()
|
|
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.Id = 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 IptablePort uint16
|
|
|
|
type IptableType string
|
|
|
|
const (
|
|
IptableTypeRule = "rule"
|
|
IptableTypeChain = "chain"
|
|
)
|
|
|
|
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:"-"`
|
|
parsedURI *url.URL `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"`
|
|
ChainLength uint `json:"-" yaml:"-"`
|
|
|
|
ResourceType IptableType `json:"resourcetype,omitempty" yaml:"resourcetype,omitempty"`
|
|
CreateCommand *command.Command `yaml:"-" json:"-"`
|
|
ReadCommand *command.Command `yaml:"-" json:"-"`
|
|
UpdateCommand *command.Command `yaml:"-" json:"-"`
|
|
DeleteCommand *command.Command `yaml:"-" json:"-"`
|
|
|
|
config data.ConfigurationValueGetter
|
|
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() *Iptable {
|
|
i := &Iptable{ ResourceType: IptableTypeRule, Common: &Common{ resourceType: IptableTypeName } }
|
|
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.ResourceType.NewCRUD()
|
|
return i
|
|
}
|
|
|
|
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_create":
|
|
if e := i.Create(ctx); e == nil {
|
|
if triggerErr := i.stater.Trigger("created"); triggerErr == nil {
|
|
return
|
|
}
|
|
}
|
|
i.Common.State = "absent"
|
|
case "present":
|
|
i.Common.State = "present"
|
|
}
|
|
case machine.EXITSTATEEVENT:
|
|
}
|
|
}
|
|
|
|
func (i *Iptable) URI() string {
|
|
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
|
|
}
|
|
|
|
func (i *Iptable) SetURI(uri string) (err error) {
|
|
i.parsedURI, err = url.Parse(uri)
|
|
if err == nil {
|
|
fields := strings.FieldsFunc(i.parsedURI.Path, func(c rune) bool { return c == '/' })
|
|
fieldsLen := len(fields)
|
|
if i.parsedURI.Scheme == "iptable" && fieldsLen > 0 {
|
|
i.Table = IptableName(i.parsedURI.Hostname())
|
|
if err = i.Table.Validate(); err != nil {
|
|
return err
|
|
}
|
|
i.Chain = IptableChain(fields[0])
|
|
if fieldsLen < 2 {
|
|
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, uri)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (i *Iptable) UseConfig(config data.ConfigurationValueGetter) {
|
|
i.config = config
|
|
}
|
|
|
|
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 (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] == '-' {
|
|
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] == '-' {
|
|
return i.Flags
|
|
}
|
|
}
|
|
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
|
|
for index, flag := range flags {
|
|
if flag[0] == '-' {
|
|
value := flags[index + 1]
|
|
switch v := i.GetFlagValue(flag).(type) {
|
|
case []string:
|
|
for _,element := range v {
|
|
if element == value {
|
|
continue
|
|
}
|
|
}
|
|
match = false
|
|
case []ExtensionFlag:
|
|
for _,element := range v {
|
|
if element.Name == flag && element.Value == value {
|
|
continue
|
|
}
|
|
}
|
|
match = false
|
|
case IptableCIDR:
|
|
if v == IptableCIDR(value) {
|
|
continue
|
|
}
|
|
match = false
|
|
case IptableName:
|
|
if v == IptableName(value) {
|
|
continue
|
|
}
|
|
match = false
|
|
case IptableChain:
|
|
if v == IptableChain(value) {
|
|
continue
|
|
}
|
|
match = false
|
|
default:
|
|
if v.(string) == value {
|
|
continue
|
|
}
|
|
match = false
|
|
}
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (i *Iptable) ReadChainLength() error {
|
|
c := command.NewCommand()
|
|
c.Path = "iptables"
|
|
c.Args = []command.CommandArg{
|
|
command.CommandArg("-S"),
|
|
command.CommandArg("{{ .Chain }}"),
|
|
}
|
|
output,err := c.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) error {
|
|
if i.Id > 0 {
|
|
if lenErr := i.ReadChainLength(); lenErr != nil {
|
|
return lenErr
|
|
}
|
|
}
|
|
_, err := i.CreateCommand.Execute(i)
|
|
//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 nil
|
|
}
|
|
|
|
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) error {
|
|
return nil
|
|
}
|
|
|
|
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 le .Id .ChainLength }}-R{{ else }}-A{{ end }}"),
|
|
command.CommandArg("{{ .Chain }}"),
|
|
command.CommandArg("{{ if le .Id .ChainLength }}{{ .Id }}{{ 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 }}"),
|
|
}
|
|
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.Id = 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.Id = 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 {
|
|
return nil
|
|
}
|
|
|
|
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.Id = 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 {
|
|
return nil
|
|
}
|
|
|