From b27974601742c340de2240b8a389b5dc1c529b02 Mon Sep 17 00:00:00 2001 From: Samuel Lorch Date: Wed, 1 Mar 2023 11:10:33 +0100 Subject: [PATCH] initial nftables config generation test --- .gitignore | 4 ++ api/config.go | 27 +++++++++++ api/main.go | 29 +++++++++++ go.mod | 5 ++ go.sum | 2 + pkg/definitions/config.go | 6 +++ pkg/definitions/netfilter.go | 7 +++ pkg/definitions/rules.go | 23 +++++++++ pkg/nftables/config.go | 36 ++++++++++++++ pkg/nftables/template.go | 18 +++++++ pkg/nftables/template/addresses.tmpl | 0 .../template/destination_nat_rules.tmpl | 3 ++ pkg/nftables/template/forward_rules.tmpl | 3 ++ pkg/nftables/template/inbound_rules.tmpl | 0 pkg/nftables/template/nftables.tmpl | 48 +++++++++++++++++++ pkg/nftables/template/rule_match.tmpl | 1 + pkg/nftables/template/source_nat_rules.tmpl | 3 ++ 17 files changed, 215 insertions(+) create mode 100644 .gitignore create mode 100644 api/config.go create mode 100644 api/main.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/definitions/config.go create mode 100644 pkg/definitions/netfilter.go create mode 100644 pkg/definitions/rules.go create mode 100644 pkg/nftables/config.go create mode 100644 pkg/nftables/template.go create mode 100644 pkg/nftables/template/addresses.tmpl create mode 100644 pkg/nftables/template/destination_nat_rules.tmpl create mode 100644 pkg/nftables/template/forward_rules.tmpl create mode 100644 pkg/nftables/template/inbound_rules.tmpl create mode 100644 pkg/nftables/template/nftables.tmpl create mode 100644 pkg/nftables/template/rule_match.tmpl create mode 100644 pkg/nftables/template/source_nat_rules.tmpl diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e9240d3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +config.json +nftables.conf +go.work +bin/* \ No newline at end of file diff --git a/api/config.go b/api/config.go new file mode 100644 index 0000000..3e99b6a --- /dev/null +++ b/api/config.go @@ -0,0 +1,27 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + + "github.con/speatzle/nfsense/pkg/definitions" +) + +func LoadConfiguration(file string) (*definitions.Config, error) { + var config definitions.Config + configFile, err := os.Open(file) + if err != nil { + return nil, fmt.Errorf("opening Config File %w", err) + } + defer configFile.Close() + if err != nil { + fmt.Println(err.Error()) + } + jsonParser := json.NewDecoder(configFile) + err = jsonParser.Decode(&config) + if err != nil { + return nil, fmt.Errorf("decoding Config File %w", err) + } + return &config, nil +} diff --git a/api/main.go b/api/main.go new file mode 100644 index 0000000..d342b74 --- /dev/null +++ b/api/main.go @@ -0,0 +1,29 @@ +package main + +import ( + "github.con/speatzle/nfsense/pkg/nftables" + "golang.org/x/exp/slog" +) + +func main() { + slog.Info("Starting...") + conf, err := LoadConfiguration("config.json") + if err != nil { + slog.Error("Loading Config", err) + return + } + slog.Info("Config Loaded", "config", conf) + + fileContent, err := nftables.GenerateNfTablesFile(*conf) + if err != nil { + slog.Error("Generating nftables file", err) + return + } + + err = nftables.ApplyNfTablesFile(fileContent) + if err != nil { + slog.Error("Applying nftables", err) + return + } + slog.Info("Wrote nftables File!") +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..1d8606c --- /dev/null +++ b/go.mod @@ -0,0 +1,5 @@ +module github.con/speatzle/nfsense + +go 1.19 + +require golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..0661dd6 --- /dev/null +++ b/go.sum @@ -0,0 +1,2 @@ +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2 h1:Jvc7gsqn21cJHCmAWx0LiimpP18LZmUxkT5Mp7EZ1mI= +golang.org/x/exp v0.0.0-20230224173230-c95f2b4c22f2/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc= diff --git a/pkg/definitions/config.go b/pkg/definitions/config.go new file mode 100644 index 0000000..f90fdc5 --- /dev/null +++ b/pkg/definitions/config.go @@ -0,0 +1,6 @@ +package definitions + +type Config struct { + ConfigVersion int64 `json:"config_version"` + Netfilter Netfilter `json:"netfilter"` +} diff --git a/pkg/definitions/netfilter.go b/pkg/definitions/netfilter.go new file mode 100644 index 0000000..d18d5ef --- /dev/null +++ b/pkg/definitions/netfilter.go @@ -0,0 +1,7 @@ +package definitions + +type Netfilter struct { + ForwardRules []ForwardRule `json:"forward_rules"` + DestinationNATRules []DestinationNATRule `json:"destination_nat_rules"` + SourceNATRules []SourceNATRule `json:"source_nat_rules"` +} diff --git a/pkg/definitions/rules.go b/pkg/definitions/rules.go new file mode 100644 index 0000000..d7610eb --- /dev/null +++ b/pkg/definitions/rules.go @@ -0,0 +1,23 @@ +package definitions + +type Rule struct { + Match RuleMatch `json:"match"` + Comment string `json:"comment"` + Counter bool `json:"counter"` +} + +type RuleMatch struct { + TCPDestinationPort uint64 `json:"tcp_destination_port"` +} + +type ForwardRule struct { + Rule +} + +type DestinationNATRule struct { + Rule +} + +type SourceNATRule struct { + Rule +} diff --git a/pkg/nftables/config.go b/pkg/nftables/config.go new file mode 100644 index 0000000..fdc89eb --- /dev/null +++ b/pkg/nftables/config.go @@ -0,0 +1,36 @@ +package nftables + +import ( + "bytes" + "fmt" + "os" + + "github.con/speatzle/nfsense/pkg/definitions" +) + +func GenerateNfTablesFile(conf definitions.Config) (string, error) { + buf := new(bytes.Buffer) + err := templates.ExecuteTemplate(buf, "nftables.tmpl", conf) + if err != nil { + return "", fmt.Errorf("executing template: %w", err) + } + return buf.String(), nil +} + +func ApplyNfTablesFile(content string) error { + f, err := os.Create("nftables.conf") + if err != nil { + return fmt.Errorf("creating File: %w", err) + } + + _, err = f.WriteString(content + "\n") + if err != nil { + return fmt.Errorf("writing File: %w", err) + } + + err = f.Sync() + if err != nil { + return fmt.Errorf("syncing File: %w", err) + } + return nil +} diff --git a/pkg/nftables/template.go b/pkg/nftables/template.go new file mode 100644 index 0000000..163ed28 --- /dev/null +++ b/pkg/nftables/template.go @@ -0,0 +1,18 @@ +package nftables + +import ( + "embed" + "text/template" +) + +//go:embed template +var templateFS embed.FS +var templates *template.Template + +func init() { + var err error + templates, err = template.ParseFS(templateFS, "template/*.tmpl") + if err != nil { + panic(err) + } +} diff --git a/pkg/nftables/template/addresses.tmpl b/pkg/nftables/template/addresses.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/pkg/nftables/template/destination_nat_rules.tmpl b/pkg/nftables/template/destination_nat_rules.tmpl new file mode 100644 index 0000000..2f16efb --- /dev/null +++ b/pkg/nftables/template/destination_nat_rules.tmpl @@ -0,0 +1,3 @@ +{{range $rule := .Netfilter.DestinationNATRules}} + {{template "rule_match.tmpl" .Match}} {{ if $rule.Counter }} counter {{ end }} {{ if ne $rule.Comment "" }} comment "{{ $rule.Comment }}" {{ end }} +{{end}} \ No newline at end of file diff --git a/pkg/nftables/template/forward_rules.tmpl b/pkg/nftables/template/forward_rules.tmpl new file mode 100644 index 0000000..5843178 --- /dev/null +++ b/pkg/nftables/template/forward_rules.tmpl @@ -0,0 +1,3 @@ +{{range $rule := .Netfilter.ForwardRules}} + {{template "rule_match.tmpl" .Match}} {{ if $rule.Counter }} counter {{ end }} {{ if ne $rule.Comment "" }} comment "{{ $rule.Comment }}" {{ end }} +{{end}} diff --git a/pkg/nftables/template/inbound_rules.tmpl b/pkg/nftables/template/inbound_rules.tmpl new file mode 100644 index 0000000..e69de29 diff --git a/pkg/nftables/template/nftables.tmpl b/pkg/nftables/template/nftables.tmpl new file mode 100644 index 0000000..5a0675f --- /dev/null +++ b/pkg/nftables/template/nftables.tmpl @@ -0,0 +1,48 @@ +#!/usr/sbin/nft -f + +flush ruleset + +# Address object ipsets +{{template "addresses.tmpl" .}} + +# nfsense nftables inet (ipv4 + ipv6) table +table inet nfsense_inet { + + # Inbound Rules + chain inbound { + type filter hook input priority 0; policy drop; + + # Allow traffic from established and related packets, drop invalid + ct state vmap { established : accept, related : accept, invalid : drop } + + # allow loopback traffic, anything else jump to chain for further evaluation + iifname vmap { lo : accept, $DEV_WORLD : jump inbound_world, $DEV_PRIVATE : jump inbound_private } + + {{template "inbound_rules.tmpl" .}} + } + + # Forward Rules + chain forward { + type filter hook forward priority 0; policy drop; + + # Allow traffic from established and related packets, drop invalid + ct state vmap { established : accept, related : accept, invalid : drop } + + {{template "forward_rules.tmpl" .}} + } + + # Destination NAT Rules + chain prerouting { + type nat hook prerouting priority -100; policy accept; + + {{template "destination_nat_rules.tmpl" .}} + } + + # Source NAT Rules + chain postrouting { + type nat hook postrouting priority 100; policy accept; + + {{template "source_nat_rules.tmpl" .}} + } +} + diff --git a/pkg/nftables/template/rule_match.tmpl b/pkg/nftables/template/rule_match.tmpl new file mode 100644 index 0000000..e74c639 --- /dev/null +++ b/pkg/nftables/template/rule_match.tmpl @@ -0,0 +1 @@ + tcp dport {{ .TCPDestinationPort }} \ No newline at end of file diff --git a/pkg/nftables/template/source_nat_rules.tmpl b/pkg/nftables/template/source_nat_rules.tmpl new file mode 100644 index 0000000..1340aee --- /dev/null +++ b/pkg/nftables/template/source_nat_rules.tmpl @@ -0,0 +1,3 @@ +{{range $rule := .Netfilter.SourceNATRules}} + {{template "rule_match.tmpl" .Match}} {{ if $rule.Counter }} counter {{ end }} {{ if ne $rule.Comment "" }} comment "{{ $rule.Comment }}" {{ end }} +{{end}} \ No newline at end of file