jx/internal/resource/iptables.go

308 lines
7.6 KiB
Go
Raw Normal View History

2024-04-25 07:45:05 +00:00
// 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"
)
func init() {
ResourceTypes.Register("iptable", func(u *url.URL) Resource {
i := NewIptable()
i.Table = IptableName(u.Hostname())
fields := strings.Split(u.Path, "/")
slog.Info("iptables factory", "iptable", i, "uri", u, "field", fields)
i.Chain = IptableChain(fields[1])
id, _ := strconv.ParseUint(fields[2], 10, 32)
i.Id = uint(id)
return i
})
}
type IptableIPVersion string
const (
IptableIPv4 IptableIPVersion = "ipv4"
IPtableIPv6 IptableIPVersion = "ipv6"
)
type IptableName string
const (
IptableNameFilter = "filter"
IptableNameNat = "nat"
IptableNameMangel = "mangle"
IptableNameRaw = "raw"
IptableNameSecurity = "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
// Manage the state of iptables rules
// iptable://filter/INPUT/0
type Iptable struct {
Id uint `json:"id" yaml:"id"`
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" yaml:"jump"`
State string `json:"state" yaml:"state"`
CreateCommand *Command `yaml:"-" json:"-"`
ReadCommand *Command `yaml:"-" json:"-"`
UpdateCommand *Command `yaml:"-" json:"-"`
DeleteCommand *Command `yaml:"-" json:"-"`
}
func NewIptable() *Iptable {
i := &Iptable{}
i.CreateCommand, i.ReadCommand, i.UpdateCommand, i.DeleteCommand = i.NewCRUD()
return i
}
func (i *Iptable) Clone() Resource {
return &Iptable {
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,
State: i.State,
}
}
func (i *Iptable) URI() string {
return fmt.Sprintf("iptable://%s/%s/%d", i.Table, i.Chain, i.Id)
}
func (i *Iptable) SetURI(uri string) error {
resourceUri, e := url.Parse(uri)
if e == nil {
if resourceUri.Scheme == "iptable" {
i.Table = IptableName(resourceUri.Hostname())
fields := strings.Split(resourceUri.Path, "/")
i.Chain = IptableChain(fields[1])
id, _ := strconv.ParseUint(fields[2], 10, 32)
i.Id = uint(id)
} else {
e = fmt.Errorf("%w: %s is not an iptable rule", ErrInvalidResourceURI, uri)
}
}
return e
}
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.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.NewCRUD()
return nil
}
func (i *Iptable) NewCRUD() (create *Command, read *Command, update *Command, del *Command) {
return NewIptableCreateCommand(), NewIptableReadCommand(), NewIptableUpdateCommand(), NewIptableDeleteCommand()
}
func (i *Iptable) Apply() error {
switch i.State {
case "absent":
case "present":
}
return nil
}
func (i *Iptable) Load(r io.Reader) error {
return NewYAMLDecoder(r).Decode(i)
}
func (i *Iptable) LoadDecl(yamlResourceDeclaration string) error {
return NewYAMLStringDecoder(yamlResourceDeclaration).Decode(i)
}
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) 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) Type() string { return "iptable" }
func NewIptableCreateCommand() *Command {
c := NewCommand()
c.Path = "iptables"
c.Args = []CommandArg{
CommandArg("-t"),
CommandArg("{{ .Table }}"),
CommandArg("-R"),
CommandArg("{{ .Chain }}"),
CommandArg("{{ .Id }}"),
CommandArg("{{ if .In }}-i {{ .In }}{{ else if .Out }}-o {{ .Out }}{{ end }}"),
CommandArg("{{ range .Match }}-m {{ . }} {{ end }}"),
CommandArg("{{ if .Source }}-s {{ .Source }}{{ end }}"),
CommandArg("{{ if .Destination }}-d {{ .Destination }}{{ end }}"),
CommandArg("{{ if .Jump }}-j {{ .Jump }}{{ end }}"),
}
return c
}
func NewIptableReadCommand() *Command {
c := NewCommand()
c.Path = "iptables"
c.Args = []CommandArg{
CommandArg("-t"),
CommandArg("{{ .Table }}"),
CommandArg("-S"),
CommandArg("{{ .Chain }}"),
CommandArg("{{ .Id }}"),
}
c.Extractor = func(out []byte, target any) error {
i := target.(*Iptable)
ruleFields := strings.Split(strings.TrimSpace(string(out)), " ")
switch ruleFields[0] {
case "-A":
//chain := ruleFields[1]
flags := ruleFields[2:]
for optind,opt := range flags {
if optind > len(flags) - 2 {
break
}
optValue := flags[optind + 1]
switch opt {
case "-i":
i.In = optValue
case "-o":
i.Out = optValue
case "-m":
i.Match = append(i.Match, optValue)
case "-s":
i.Source = IptableCIDR(optValue)
case "-d":
i.Destination = IptableCIDR(optValue)
case "-p":
i.Proto = IptableProto(optValue)
case "-j":
i.Jump = optValue
case "--dport":
port,_ := strconv.ParseUint(optValue, 10, 16)
i.Dport = IptablePort(port)
case "--sport":
port,_ := strconv.ParseUint(optValue, 10, 16)
i.Sport = IptablePort(port)
default:
if opt[0] == '-' {
i.Flags = append(i.Flags, ExtensionFlag{ Name: strings.Trim(opt, "-"), Value: strings.TrimSpace(optValue)})
}
}
}
i.State = "present"
default:
i.State = "absent"
}
return nil
}
return c
}
func NewIptableUpdateCommand() *Command {
return nil
}
func NewIptableDeleteCommand() *Command {
return nil
}