diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 63c014e75..a546bb685 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -35,6 +35,9 @@ jobs: run: | sudo apt-get update sudo apt-get install linux-modules-extra-$(uname -r) + - name: Install nftables + run: sudo apt-get install nftables + - name: setup go uses: actions/setup-go@v2 with: diff --git a/go.mod b/go.mod index 7a8a74a2a..d60589431 100644 --- a/go.mod +++ b/go.mod @@ -15,6 +15,7 @@ require ( github.com/godbus/dbus/v5 v5.0.4 github.com/j-keck/arping v1.0.2 github.com/mattn/go-shellwords v1.0.12 + github.com/networkplumbing/go-nft v0.1.1 github.com/onsi/ginkgo v1.16.4 github.com/onsi/gomega v1.15.0 github.com/safchain/ethtool v0.0.0-20210803160452-9aa261dae9b1 diff --git a/go.sum b/go.sum index f8b5cbf26..4d36e0889 100644 --- a/go.sum +++ b/go.sum @@ -436,6 +436,8 @@ github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8m github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= github.com/ncw/swift v1.0.47/go.mod h1:23YIA4yWVnGwv2dQlN4bB7egfYX6YLn0Yo/S6zZO/ZM= +github.com/networkplumbing/go-nft v0.1.1 h1:L8ynfgoXg6JKdjSerg0bN27ZsEtMi+KkCe/Q5gR2FN0= +github.com/networkplumbing/go-nft v0.1.1/go.mod h1:HnnM+tYvlGAsMU7yoYwXEVLLiDW9gdMmb5HoGcwpuQs= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= @@ -565,8 +567,9 @@ github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXf github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/syndtr/gocapability v0.0.0-20170704070218-db04d3cc01c8/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20180916011248-d98352740cb2/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= github.com/syndtr/gocapability v0.0.0-20200815063812-42c35b437635/go.mod h1:hkRG7XYTFWNJGYcbNJQlaLq0fg1yr4J4t/NcTQtrfww= @@ -597,6 +600,7 @@ github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= github.com/yvasiyarov/go-metrics v0.0.0-20140926110328-57bccd1ccd43/go.mod h1:aX5oPXxHm3bOH+xeAttToC8pqch2ScQN/JoXYupl6xs= github.com/yvasiyarov/gorelic v0.0.0-20141212073537-a9bba5b9ab50/go.mod h1:NUSPSUX/bi6SeDMUh6brw0nXpxHnc96TguQh0+r/ssA= github.com/yvasiyarov/newrelic_platform_go v0.0.0-20140908184405-b21fdbd4370f/go.mod h1:GlGEuHIJweS1mbCqG+7vt2nvWLzLLnRHbXz5JKd/Qbg= @@ -657,6 +661,7 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -692,6 +697,7 @@ golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwY golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 h1:DzZ89McO9/gWPsQXS/FVKAlG02ZjaQ6AlZRBimEYOd0= golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= @@ -708,6 +714,7 @@ golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -769,7 +776,9 @@ golang.org/x/sys v0.0.0-20201202213521-69691e467435/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e h1:WUoyKPm6nCo1BnNUvPGnFG3T5DUVem42yDJZZ4CNxMA= golang.org/x/sys v0.0.0-20210809222454-d867a43fc93e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= @@ -824,6 +833,7 @@ golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjs golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.4/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/pkg/link/link_suite_test.go b/pkg/link/link_suite_test.go new file mode 100644 index 000000000..02364a34f --- /dev/null +++ b/pkg/link/link_suite_test.go @@ -0,0 +1,27 @@ +// Copyright 2021 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link_test + +import ( + "testing" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +func TestIp(t *testing.T) { + RegisterFailHandler(Fail) + RunSpecs(t, "pkg/link") +} diff --git a/pkg/link/spoofcheck.go b/pkg/link/spoofcheck.go new file mode 100644 index 000000000..ba8ad7a62 --- /dev/null +++ b/pkg/link/spoofcheck.go @@ -0,0 +1,237 @@ +// Copyright 2021 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link + +import ( + "fmt" + "os" + + "github.com/networkplumbing/go-nft/nft" + "github.com/networkplumbing/go-nft/nft/schema" +) + +const ( + natTableName = "nat" + preRoutingBaseChainName = "PREROUTING" +) + +type applyAction func(*nft.Config) error +type readAction func() (*nft.Config, error) + +type SpoofChecker struct { + iface string + macAddress string + refID string + applyFunc applyAction + readFunc readAction +} + +func NewSpoofChecker(iface, macAddress, refID string) *SpoofChecker { + return &SpoofChecker{iface, macAddress, refID, nft.ApplyConfig, nft.ReadConfig} +} + +func NewSpoofCheckerWithCustomActions(iface, macAddress, refID string, applyF applyAction, readF readAction) *SpoofChecker { + sc := NewSpoofChecker(iface, macAddress, refID) + sc.applyFunc = applyF + sc.readFunc = readF + return sc +} + +// Setup applies nftables configuration to restrict traffic +// from the provided interface. Only traffic with the mentioned mac address +// is allowed to pass, all others are blocked. +// The configuration follows the format libvirt and ebtables implemented, allowing +// extensions to the rules in the future. +// refID is used to label the rules with a unique comment, identifying the rule-set. +// +// In order to take advantage of the nftables configuration change atomicity, the +// following steps are taken to apply the configuration: +// - Declare the table and chains (they will be created in case not present). +// - Apply the rules, while first flushing the iface/mac specific regular chain rules. +// Two transactions are used because the flush succeeds only if the table/chain it targets +// exists. This avoids the need to query the existing state and acting upon it (a raceful pattern). +// Although two transactions are taken place, only the 2nd one where the rules +// are added has a real impact on the system. +func (sc *SpoofChecker) Setup() error { + baseConfig := nft.NewConfig() + + baseConfig.AddTable(&schema.Table{Family: schema.FamilyBridge, Name: natTableName}) + + baseConfig.AddChain(sc.baseChain()) + ifaceChain := sc.ifaceChain() + baseConfig.AddChain(ifaceChain) + macChain := sc.macChain(ifaceChain.Name) + baseConfig.AddChain(macChain) + + if err := sc.applyFunc(baseConfig); err != nil { + return fmt.Errorf("failed to setup spoof-check: %v", err) + } + + rulesConfig := nft.NewConfig() + + rulesConfig.FlushChain(ifaceChain) + rulesConfig.FlushChain(macChain) + + rulesConfig.AddRule(sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name)) + rulesConfig.AddRule(sc.jumpToChainRule(ifaceChain.Name, macChain.Name)) + rulesConfig.AddRule(sc.matchMacRule(macChain.Name)) + rulesConfig.AddRule(sc.dropRule(macChain.Name)) + + if err := sc.applyFunc(rulesConfig); err != nil { + return fmt.Errorf("failed to setup spoof-check: %v", err) + } + + return nil +} + +// Teardown removes the interface and mac-address specific chains and their rules. +// The table and base-chain are expected to survive while the base-chain rule that matches the +// interface is removed. +func (sc *SpoofChecker) Teardown() error { + ifaceChain := sc.ifaceChain() + currentConfig, ifaceMatchRuleErr := sc.readFunc() + if ifaceMatchRuleErr == nil { + expectedRuleToFind := sc.matchIfaceJumpToChainRule(preRoutingBaseChainName, ifaceChain.Name) + // It is safer to exclude the statement matching, avoiding cases where a current statement includes + // additional default entries (e.g. counters). + ruleToFindExcludingStatements := *expectedRuleToFind + ruleToFindExcludingStatements.Expr = nil + rules := currentConfig.LookupRule(&ruleToFindExcludingStatements) + if len(rules) > 0 { + c := nft.NewConfig() + for _, rule := range rules { + c.DeleteRule(rule) + } + if err := sc.applyFunc(c); err != nil { + ifaceMatchRuleErr = fmt.Errorf("failed to delete iface match rule: %v", err) + } + } else { + fmt.Fprintf(os.Stderr, "spoofcheck/teardown: unable to detect iface match rule for deletion: %+v", expectedRuleToFind) + } + } + + regularChainsConfig := nft.NewConfig() + regularChainsConfig.DeleteChain(ifaceChain) + regularChainsConfig.DeleteChain(sc.macChain(ifaceChain.Name)) + + var regularChainsErr error + if err := sc.applyFunc(regularChainsConfig); err != nil { + regularChainsErr = fmt.Errorf("failed to delete regular chains: %v", err) + } + + if ifaceMatchRuleErr != nil || regularChainsErr != nil { + return fmt.Errorf("failed to teardown spoof-check: %v, %v", ifaceMatchRuleErr, regularChainsErr) + } + return nil +} + +func (sc *SpoofChecker) matchIfaceJumpToChainRule(chain, toChain string) *schema.Rule { + return &schema.Rule{ + Family: schema.FamilyBridge, + Table: natTableName, + Chain: chain, + Expr: []schema.Statement{ + {Match: &schema.Match{ + Op: schema.OperEQ, + Left: schema.Expression{RowData: []byte(`{"meta":{"key":"iifname"}}`)}, + Right: schema.Expression{String: &sc.iface}, + }}, + {Verdict: schema.Verdict{Jump: &schema.ToTarget{Target: toChain}}}, + }, + Comment: ruleComment(sc.refID), + } +} + +func (sc *SpoofChecker) jumpToChainRule(chain, toChain string) *schema.Rule { + return &schema.Rule{ + Family: schema.FamilyBridge, + Table: natTableName, + Chain: chain, + Expr: []schema.Statement{ + {Verdict: schema.Verdict{Jump: &schema.ToTarget{Target: toChain}}}, + }, + Comment: ruleComment(sc.refID), + } +} + +func (sc *SpoofChecker) matchMacRule(chain string) *schema.Rule { + return &schema.Rule{ + Family: schema.FamilyBridge, + Table: natTableName, + Chain: chain, + Expr: []schema.Statement{ + {Match: &schema.Match{ + Op: schema.OperEQ, + Left: schema.Expression{Payload: &schema.Payload{ + Protocol: schema.PayloadProtocolEther, + Field: schema.PayloadFieldEtherSAddr, + }}, + Right: schema.Expression{String: &sc.macAddress}, + }}, + {Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Return: true}}}, + }, + Comment: ruleComment(sc.refID), + } +} + +func (sc *SpoofChecker) dropRule(chain string) *schema.Rule { + macRulesIndex := nft.NewRuleIndex() + return &schema.Rule{ + Family: schema.FamilyBridge, + Table: natTableName, + Chain: chain, + Index: macRulesIndex.Next(), + Expr: []schema.Statement{ + {Verdict: schema.Verdict{SimpleVerdict: schema.SimpleVerdict{Drop: true}}}, + }, + Comment: ruleComment(sc.refID), + } +} + +func (_ *SpoofChecker) baseChain() *schema.Chain { + chainPriority := -300 + return &schema.Chain{ + Family: schema.FamilyBridge, + Table: natTableName, + Name: preRoutingBaseChainName, + Type: schema.TypeFilter, + Hook: schema.HookPreRouting, + Prio: &chainPriority, + Policy: schema.PolicyAccept, + } +} + +func (sc *SpoofChecker) ifaceChain() *schema.Chain { + ifaceChainName := "cni-br-iface-" + sc.refID + return &schema.Chain{ + Family: schema.FamilyBridge, + Table: natTableName, + Name: ifaceChainName, + } +} + +func (_ *SpoofChecker) macChain(ifaceChainName string) *schema.Chain { + macChainName := ifaceChainName + "-mac" + return &schema.Chain{ + Family: schema.FamilyBridge, + Table: natTableName, + Name: macChainName, + } +} + +func ruleComment(id string) string { + const refIDPrefix = "macspoofchk-" + return refIDPrefix + id +} diff --git a/pkg/link/spoofcheck_test.go b/pkg/link/spoofcheck_test.go new file mode 100644 index 000000000..4b5d19910 --- /dev/null +++ b/pkg/link/spoofcheck_test.go @@ -0,0 +1,297 @@ +// Copyright 2021 CNI authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package link_test + +import ( + "fmt" + "github.com/networkplumbing/go-nft/nft" + + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + + "github.com/containernetworking/plugins/pkg/link" +) + +var _ = Describe("spoofcheck", func() { + iface := "net0" + mac := "02:00:00:00:12:34" + id := "container99-net1" + + Context("setup", func() { + It("succeeds", func() { + action := actionStub{} + sc := link.NewSpoofCheckerWithCustomActions(iface, mac, id, action.applyConfigStub, nil) + Expect(sc.Setup()).To(Succeed()) + + assertExpectedTableAndChainsInSetupConfig(action) + assertExpectedRulesInSetupConfig(action) + }) + + It("fails to setup config when 1st apply is unsuccessful (declare table and chains)", func() { + action := actionStub{failFirstApplyConfig: true} + sc := link.NewSpoofCheckerWithCustomActions(iface, mac, id, action.applyConfigStub, nil) + Expect(sc.Setup()).To(MatchError("failed to setup spoof-check: " + errorFirstApplyText)) + }) + + It("fails to setup config when 2nd apply is unsuccessful (flush and add the rules)", func() { + action := actionStub{failSecondApplyConfig: true} + sc := link.NewSpoofCheckerWithCustomActions(iface, mac, id, action.applyConfigStub, nil) + Expect(sc.Setup()).To(MatchError("failed to setup spoof-check: " + errorSecondApplyText)) + }) + }) + + Context("teardown", func() { + It("succeeds", func() { + existingConfig := nft.NewConfig() + existingConfig.FromJSON([]byte(rowConfigWithRulesOnly())) + action := actionStub{readConfig: existingConfig} + + sc := link.NewSpoofCheckerWithCustomActions("", "", id, action.applyConfigStub, action.readConfigStub) + Expect(sc.Teardown()).To(Succeed()) + + assertExpectedBaseChainRuleDeletionInTeardownConfig(action) + assertExpectedRegularChainsDeletionInTeardownConfig(action) + }) + + It("fails, 1st apply is unsuccessful (delete iface match rule)", func() { + config := nft.NewConfig() + config.FromJSON([]byte(rowConfigWithRulesOnly())) + action := actionStub{applyConfig: []*nft.Config{config}, readConfig: config, failFirstApplyConfig: true} + sc := link.NewSpoofCheckerWithCustomActions("", "", id, action.applyConfigStub, action.readConfigStub) + Expect(sc.Teardown()).To(MatchError(fmt.Sprintf( + "failed to teardown spoof-check: failed to delete iface match rule: %s, ", errorFirstApplyText, + ))) + }) + + It("fails, read current config is unsuccessful", func() { + config := nft.NewConfig() + config.FromJSON([]byte(rowConfigWithRulesOnly())) + action := actionStub{applyConfig: []*nft.Config{config}, readConfig: config, failReadConfig: true} + sc := link.NewSpoofCheckerWithCustomActions("", "", id, action.applyConfigStub, action.readConfigStub) + Expect(sc.Teardown()).To(MatchError(fmt.Sprintf( + "failed to teardown spoof-check: %s, ", errorReadText, + ))) + }) + + It("fails, 2nd apply is unsuccessful (delete the regular chains)", func() { + config := nft.NewConfig() + config.FromJSON([]byte(rowConfigWithRulesOnly())) + action := actionStub{applyConfig: []*nft.Config{config}, readConfig: config, failSecondApplyConfig: true} + sc := link.NewSpoofCheckerWithCustomActions("", "", id, action.applyConfigStub, action.readConfigStub) + Expect(sc.Teardown()).To(MatchError(fmt.Sprintf( + "failed to teardown spoof-check: , failed to delete regular chains: %s", errorSecondApplyText, + ))) + }) + + It("fails, both applies are unsuccessful", func() { + config := nft.NewConfig() + config.FromJSON([]byte(rowConfigWithRulesOnly())) + action := actionStub{ + applyConfig: []*nft.Config{config}, + readConfig: config, + failFirstApplyConfig: true, + failSecondApplyConfig: true, + } + sc := link.NewSpoofCheckerWithCustomActions("", "", id, action.applyConfigStub, action.readConfigStub) + Expect(sc.Teardown()).To(MatchError(fmt.Sprintf( + "failed to teardown spoof-check: "+ + "failed to delete iface match rule: %s, "+ + "failed to delete regular chains: %s", + errorFirstApplyText, errorSecondApplyText, + ))) + }) + }) +}) + +func assertExpectedRegularChainsDeletionInTeardownConfig(action actionStub) { + deleteRegularChainRulesJsonConfig, err := action.applyConfig[1].ToJSON() + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + expectedDeleteRegularChainRulesJsonConfig := ` + {"nftables": [ + {"delete": {"chain": { + "family": "bridge", + "table": "nat", + "name": "cni-br-iface-container99-net1" + }}}, + {"delete": {"chain": { + "family": "bridge", + "table": "nat", + "name": "cni-br-iface-container99-net1-mac" + }}} + ]}` + + ExpectWithOffset(1, string(deleteRegularChainRulesJsonConfig)).To(MatchJSON(expectedDeleteRegularChainRulesJsonConfig)) +} + +func assertExpectedBaseChainRuleDeletionInTeardownConfig(action actionStub) { + deleteBaseChainRuleJsonConfig, err := action.applyConfig[0].ToJSON() + Expect(err).NotTo(HaveOccurred()) + + expectedDeleteIfaceMatchRuleJsonConfig := ` + {"nftables": [ + {"delete": {"rule": { + "family": "bridge", + "table": "nat", + "chain": "PREROUTING", + "expr": [ + {"match": { + "op": "==", + "left": {"meta": {"key": "iifname"}}, + "right": "net0" + }}, + {"jump": {"target": "cni-br-iface-container99-net1"}} + ], + "comment": "macspoofchk-container99-net1" + }}} + ]}` + Expect(string(deleteBaseChainRuleJsonConfig)).To(MatchJSON(expectedDeleteIfaceMatchRuleJsonConfig)) +} + +func rowConfigWithRulesOnly() string { + return ` + {"nftables":[ + {"rule":{"family":"bridge","table":"nat","chain":"PREROUTING", + "expr":[ + {"match":{"op":"==","left":{"meta":{"key":"iifname"}},"right":"net0"}}, + {"jump":{"target":"cni-br-iface-container99-net1"}} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1", + "expr":[ + {"jump":{"target":"cni-br-iface-container99-net1-mac"}} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac", + "expr":[ + {"match":{ + "op":"==", + "left":{"payload":{"protocol":"ether","field":"saddr"}}, + "right":"02:00:00:00:12:34" + }}, + {"return":null} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac", + "expr":[{"drop":null}], + "index":0, + "comment":"macspoofchk-container99-net1"}} + ]}` +} + +func assertExpectedTableAndChainsInSetupConfig(action actionStub) { + config := action.applyConfig[0] + jsonConfig, err := config.ToJSON() + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + expectedConfig := ` + {"nftables": [ + {"table": {"family": "bridge", "name": "nat"}}, + {"chain": { + "family": "bridge", + "table": "nat", + "name": "PREROUTING", + "type": "filter", + "hook": "prerouting", + "prio": -300, + "policy": "accept" + }}, + {"chain": { + "family": "bridge", + "table": "nat", + "name": "cni-br-iface-container99-net1" + }}, + {"chain": { + "family": "bridge", + "table": "nat", + "name": "cni-br-iface-container99-net1-mac" + }} + ]}` + ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig)) +} + +func assertExpectedRulesInSetupConfig(action actionStub) { + config := action.applyConfig[1] + jsonConfig, err := config.ToJSON() + ExpectWithOffset(1, err).NotTo(HaveOccurred()) + + expectedConfig := ` + {"nftables":[ + {"flush":{"chain":{"family":"bridge","table":"nat","name":"cni-br-iface-container99-net1"}}}, + {"flush":{"chain":{"family":"bridge","table":"nat","name":"cni-br-iface-container99-net1-mac"}}}, + {"rule":{"family":"bridge","table":"nat","chain":"PREROUTING", + "expr":[ + {"match":{"op":"==","left":{"meta":{"key":"iifname"}},"right":"net0"}}, + {"jump":{"target":"cni-br-iface-container99-net1"}} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1", + "expr":[ + {"jump":{"target":"cni-br-iface-container99-net1-mac"}} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac", + "expr":[ + {"match":{ + "op":"==", + "left":{"payload":{"protocol":"ether","field":"saddr"}}, + "right":"02:00:00:00:12:34" + }}, + {"return":null} + ], + "comment":"macspoofchk-container99-net1"}}, + {"rule":{"family":"bridge","table":"nat","chain":"cni-br-iface-container99-net1-mac", + "expr":[{"drop":null}], + "index":0, + "comment":"macspoofchk-container99-net1"}} + ]}` + ExpectWithOffset(1, string(jsonConfig)).To(MatchJSON(expectedConfig)) +} + +const ( + errorFirstApplyText = "1st apply failed" + errorSecondApplyText = "2nd apply failed" + errorReadText = "read failed" +) + +type actionStub struct { + applyConfig []*nft.Config + readConfig *nft.Config + + applyCounter int + + failFirstApplyConfig bool + failSecondApplyConfig bool + failReadConfig bool +} + +func (a *actionStub) applyConfigStub(c *nft.Config) error { + a.applyCounter++ + if a.failFirstApplyConfig && a.applyCounter == 1 { + return fmt.Errorf(errorFirstApplyText) + } + if a.failSecondApplyConfig && a.applyCounter == 2 { + return fmt.Errorf(errorSecondApplyText) + } + a.applyConfig = append(a.applyConfig, c) + return nil +} + +func (a *actionStub) readConfigStub() (*nft.Config, error) { + if a.failReadConfig { + return nil, fmt.Errorf(errorReadText) + } + return a.readConfig, nil +} diff --git a/plugins/main/bridge/bridge.go b/plugins/main/bridge/bridge.go index 477c72d5d..dbce42441 100644 --- a/plugins/main/bridge/bridge.go +++ b/plugins/main/bridge/bridge.go @@ -20,6 +20,7 @@ import ( "fmt" "io/ioutil" "net" + "os" "runtime" "syscall" "time" @@ -33,6 +34,7 @@ import ( "github.com/containernetworking/cni/pkg/version" "github.com/containernetworking/plugins/pkg/ip" "github.com/containernetworking/plugins/pkg/ipam" + "github.com/containernetworking/plugins/pkg/link" "github.com/containernetworking/plugins/pkg/ns" "github.com/containernetworking/plugins/pkg/utils" bv "github.com/containernetworking/plugins/pkg/utils/buildversion" @@ -55,6 +57,7 @@ type NetConf struct { HairpinMode bool `json:"hairpinMode"` PromiscMode bool `json:"promiscMode"` Vlan int `json:"vlan"` + MacSpoofChk bool `json:"macspoofchk,omitempty"` Args struct { Cni BridgeArgs `json:"cni,omitempty"` @@ -460,6 +463,20 @@ func cmdAdd(args *skel.CmdArgs) error { }, } + if n.MacSpoofChk { + sc := link.NewSpoofChecker(hostInterface.Name, containerInterface.Mac, uniqueID(args.ContainerID, args.IfName)) + if err := sc.Setup(); err != nil { + return err + } + defer func() { + if !success { + if err := sc.Teardown(); err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + } + } + }() + } + if isLayer3 { // run the IPAM plugin and get back the config to apply r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData) @@ -658,6 +675,13 @@ func cmdDel(args *skel.CmdArgs) error { return err } + if n.MacSpoofChk { + sc := link.NewSpoofChecker("", "", uniqueID(args.ContainerID, args.IfName)) + if err := sc.Teardown(); err != nil { + fmt.Fprintf(os.Stderr, "%v", err) + } + } + if isLayer3 && n.IPMasq { chain := utils.FormatChainName(n.Name, args.ContainerID) comment := utils.FormatComment(n.Name, args.ContainerID) @@ -938,3 +962,7 @@ func cmdCheck(args *skel.CmdArgs) error { return nil } + +func uniqueID(containerID, cniIface string) string { + return containerID + "-" + cniIface +} diff --git a/plugins/main/bridge/bridge_test.go b/plugins/main/bridge/bridge_test.go index 29d08eb24..5d1e0dcb1 100644 --- a/plugins/main/bridge/bridge_test.go +++ b/plugins/main/bridge/bridge_test.go @@ -23,6 +23,7 @@ import ( "strings" "github.com/coreos/go-iptables/iptables" + "github.com/networkplumbing/go-nft/nft" "github.com/vishvananda/netlink/nl" "github.com/containernetworking/cni/pkg/skel" @@ -65,19 +66,20 @@ type Net struct { // testCase defines the CNI network configuration and the expected // bridge addresses for a test case. type testCase struct { - cniVersion string // CNI Version - subnet string // Single subnet config: Subnet CIDR - gateway string // Single subnet config: Gateway - ranges []rangeInfo // Ranges list (multiple subnets config) - isGW bool - isLayer2 bool - expGWCIDRs []string // Expected gateway addresses in CIDR form - vlan int - ipMasq bool - AddErr020 string - DelErr020 string - AddErr010 string - DelErr010 string + cniVersion string // CNI Version + subnet string // Single subnet config: Subnet CIDR + gateway string // Single subnet config: Gateway + ranges []rangeInfo // Ranges list (multiple subnets config) + isGW bool + isLayer2 bool + expGWCIDRs []string // Expected gateway addresses in CIDR form + vlan int + ipMasq bool + macspoofchk bool + AddErr020 string + DelErr020 string + AddErr010 string + DelErr010 string envArgs string // CNI_ARGS runtimeConfig struct { @@ -164,6 +166,9 @@ const ( ipamEndStr = ` }` + macspoofchkFormat = `, + "macspoofchk": %t` + argsFormat = `, "args": { "cni": { @@ -193,6 +198,9 @@ func (tc testCase) netConfJSON(dataDir string) string { if tc.runtimeConfig.mac != "" { conf += fmt.Sprintf(runtimeConfig, tc.runtimeConfig.mac) } + if tc.macspoofchk { + conf += fmt.Sprintf(macspoofchkFormat, tc.macspoofchk) + } if !tc.isLayer2 { conf += netDefault @@ -2175,6 +2183,35 @@ var _ = Describe("bridge Operations", func() { }) } } + + It(fmt.Sprintf("[%s] configures mac spoof-check (no mac spoofing)", ver), func() { + Expect(originalNS.Do(func(ns.NetNS) error { + defer GinkgoRecover() + + tc := testCase{ + cniVersion: ver, + subnet: "10.1.2.0/24", + macspoofchk: true, + } + args := tc.createCmdArgs(originalNS, dataDir) + _, _, err := testutils.CmdAddWithArgs(args, func() error { + return cmdAdd(args) + }) + Expect(err).NotTo(HaveOccurred()) + + assertMacSpoofCheckRulesExist() + + Expect(testutils.CmdDelWithArgs(args, func() error { + if err := cmdDel(args); err != nil { + return err + } + assertMacSpoofCheckRulesMissing() + return nil + })).To(Succeed()) + + return nil + })).To(Succeed()) + }) } It("check vlan id when loading net conf", func() { @@ -2211,3 +2248,50 @@ var _ = Describe("bridge Operations", func() { } }) }) + +func assertMacSpoofCheckRulesExist() { + assertMacSpoofCheckRules( + func(actual interface{}, expectedLen int) { + ExpectWithOffset(3, actual).To(HaveLen(expectedLen)) + }) +} + +func assertMacSpoofCheckRulesMissing() { + assertMacSpoofCheckRules( + func(actual interface{}, _ int) { + ExpectWithOffset(3, actual).To(BeEmpty()) + }) +} + +func assertMacSpoofCheckRules(assert func(actual interface{}, expectedLen int)) { + c, err := nft.ReadConfig() + ExpectWithOffset(2, err).NotTo(HaveOccurred()) + + expectedTable := nft.NewTable("nat", "bridge") + filter := nft.TypeFilter + hook := nft.HookPreRouting + prio := -300 + policy := nft.PolicyAccept + expectedBaseChain := nft.NewChain(expectedTable, "PREROUTING", &filter, &hook, &prio, &policy) + + assert(c.LookupRule(nft.NewRule( + expectedTable, + expectedBaseChain, + nil, nil, nil, + "macspoofchk-dummy-0-eth0", + )), 1) + + assert(c.LookupRule(nft.NewRule( + expectedTable, + nft.NewRegularChain(expectedTable, "cni-br-iface-dummy-0-eth0"), + nil, nil, nil, + "macspoofchk-dummy-0-eth0", + )), 1) + + assert(c.LookupRule(nft.NewRule( + expectedTable, + nft.NewRegularChain(expectedTable, "cni-br-iface-dummy-0-eth0-mac"), + nil, nil, nil, + "macspoofchk-dummy-0-eth0", + )), 2) +} diff --git a/plugins/meta/portmap/portmap.go b/plugins/meta/portmap/portmap.go index 480431bef..5b6d2ea59 100644 --- a/plugins/meta/portmap/portmap.go +++ b/plugins/meta/portmap/portmap.go @@ -291,7 +291,7 @@ func fillDnatRules(c *chain, config *PortMapConf, containerNet net.IPNet) { // genSetMarkChain creates the SETMARK chain - the chain that sets the // "to-be-masqueraded" mark and returns. -// Chains are idempotent, so we'll always create this. +// chains are idempotent, so we'll always create this. func genSetMarkChain(markBit int) chain { markValue := 1 << uint(markBit) markDef := fmt.Sprintf("%#x/%#x", markValue, markValue) diff --git a/vendor/github.com/networkplumbing/go-nft/LICENSE b/vendor/github.com/networkplumbing/go-nft/LICENSE new file mode 100644 index 000000000..261eeb9e9 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/vendor/github.com/networkplumbing/go-nft/nft/chain.go b/vendor/github.com/networkplumbing/go-nft/nft/chain.go new file mode 100644 index 000000000..df0678bd3 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/chain.go @@ -0,0 +1,138 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package nft + +import ( + "github.com/networkplumbing/go-nft/nft/schema" +) + +type ChainType string +type ChainHook string +type ChainPolicy string + +// Chain Types +const ( + TypeFilter ChainType = schema.TypeFilter + TypeNAT ChainType = schema.TypeNAT + TypeRoute ChainType = schema.TypeRoute +) + +// Chain Hooks +const ( + HookPreRouting ChainHook = schema.HookPreRouting + HookInput ChainHook = schema.HookInput + HookOutput ChainHook = schema.HookOutput + HookForward ChainHook = schema.HookForward + HookPostRouting ChainHook = schema.HookPostRouting + HookIngress ChainHook = schema.HookIngress +) + +// Chain Policies +const ( + PolicyAccept ChainPolicy = schema.PolicyAccept + PolicyDrop ChainPolicy = schema.PolicyDrop +) + +// NewRegularChain returns a new schema chain structure for a regular chain. +func NewRegularChain(table *schema.Table, name string) *schema.Chain { + return NewChain(table, name, nil, nil, nil, nil) +} + +// NewChain returns a new schema chain structure for a base chain. +// For base chains, all arguments are required except the policy. +// Missing arguments will cause an error once the config is applied. +func NewChain(table *schema.Table, name string, ctype *ChainType, hook *ChainHook, prio *int, policy *ChainPolicy) *schema.Chain { + c := &schema.Chain{ + Family: table.Family, + Table: table.Name, + Name: name, + } + + if ctype != nil { + c.Type = string(*ctype) + } + if hook != nil { + c.Hook = string(*hook) + } + if prio != nil { + c.Prio = prio + } + if policy != nil { + c.Policy = string(*policy) + } + + return c +} + +// AddChain appends the given chain to the nftable config. +// The chain is added without an explicit action (`add`). +// Adding multiple times the same chain has no affect when the config is applied. +func (c *Config) AddChain(chain *schema.Chain) { + nftable := schema.Nftable{Chain: chain} + c.Nftables = append(c.Nftables, nftable) +} + +// DeleteChain appends a given chain to the nftable config +// with the `delete` action. +// Attempting to delete a non-existing chain, results with a failure when the config is applied. +// The chain must not contain any rules or be used as a jump target. +func (c *Config) DeleteChain(chain *schema.Chain) { + nftable := schema.Nftable{Delete: &schema.Objects{Chain: chain}} + c.Nftables = append(c.Nftables, nftable) +} + +// FlushChain appends a given chain to the nftable config +// with the `flush` action. +// All rules under the chain are removed (when applied). +// Attempting to flush a non-existing chain, results with a failure when the config is applied. +func (c *Config) FlushChain(chain *schema.Chain) { + nftable := schema.Nftable{Flush: &schema.Objects{Chain: chain}} + c.Nftables = append(c.Nftables, nftable) +} + +// LookupChain searches the configuration for a matching chain and returns it. +// The chain is matched first by the table and chain name. +// Other matching fields are optional (for matching base chains). +// Mutating the returned chain will result in mutating the configuration. +func (c *Config) LookupChain(toFind *schema.Chain) *schema.Chain { + for _, nftable := range c.Nftables { + if chain := nftable.Chain; chain != nil { + match := chain.Table == toFind.Table && chain.Family == toFind.Family && chain.Name == toFind.Name + if match { + if t := toFind.Type; t != "" { + match = match && chain.Type == t + } + if h := toFind.Hook; h != "" { + match = match && chain.Hook == h + } + if p := toFind.Prio; p != nil { + match = match && chain.Prio != nil && *chain.Prio == *p + } + if p := toFind.Policy; p != "" { + match = match && chain.Policy == p + } + if match { + return chain + } + } + } + } + return nil +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/config.go b/vendor/github.com/networkplumbing/go-nft/nft/config.go new file mode 100644 index 000000000..7f7fb270c --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/config.go @@ -0,0 +1,59 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package nft + +import ( + "encoding/json" + + "github.com/networkplumbing/go-nft/nft/schema" +) + +type Config struct { + schema.Root +} + +// NewConfig returns a new nftables config structure. +func NewConfig() *Config { + c := &Config{} + c.Nftables = []schema.Nftable{} + return c +} + +// ToJSON returns the JSON encoding of the nftables config. +func (c *Config) ToJSON() ([]byte, error) { + return json.Marshal(*c) +} + +// FromJSON decodes the provided JSON-encoded data and populates the nftables config. +func (c *Config) FromJSON(data []byte) error { + if err := json.Unmarshal(data, c); err != nil { + return err + } + return nil +} + +// FlushRuleset adds a command to the nftables config that erases all the configuration when applied. +// It is commonly used as the first config instruction, followed by a declarative configuration. +// When used, any previous configuration is flushed away before adding the new one. +// Calling FlushRuleset updates the configuration and will take effect only +// when applied on the system. +func (c *Config) FlushRuleset() { + c.Nftables = append(c.Nftables, schema.Nftable{Flush: &schema.Objects{Ruleset: true}}) +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/doc.go b/vendor/github.com/networkplumbing/go-nft/nft/doc.go new file mode 100644 index 000000000..4d2e656c1 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/doc.go @@ -0,0 +1,48 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +// Package nft provides a GO API to nftables. +// Together with the schema package, it allows to build, read and apply +// nftables configuration on a supporting system. +// +// The schema structures are based on libnftables-json (https://www.mankier.com/5/libnftables-json) +// and implement a subset of them. +// +// To create a new configuration, use `NewConfig` followed by methods +// which populates the configuration with tables, chains and rules, accompanied +// to specific actions (add, delete, flush). +// +// config := nft.NewConfig() +// table := nft.NewTable("mytable", nft.FamilyIP) +// config.AddTable(table) +// chain := nft.NewRegularChain(table, "mychain") +// config.AddChain(chain) +// rule := nft.NewRule(table, chain, statements, nil, nil, "mycomment") +// +// To apply a configuration on the system, use the `ApplyConfig` function. +// err := nft.ApplyConfig(config) +// +// To read the configuration from the system, use the `ReadConfig` function. +// config, err := nft.ReadConfig() +// +// For full setup example, see the integration test: tests/config_test.go +// +// The nft package is dependent on the `nft` binary and the kernel nftables +// support. +package nft diff --git a/vendor/github.com/networkplumbing/go-nft/nft/exec.go b/vendor/github.com/networkplumbing/go-nft/nft/exec.go new file mode 100644 index 000000000..ed082a8bd --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/exec.go @@ -0,0 +1,91 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package nft + +import ( + "bytes" + "fmt" + "os/exec" + "strings" +) + +const ( + cmdBin = "nft" + cmdFile = "-f" + cmdJSON = "-j" + cmdList = "list" + cmdRuleset = "ruleset" + cmdStdin = "-" +) + +// ReadConfig loads the nftables configuration from the system and +// returns it as a nftables config structure. +// The system is expected to have the `nft` executable deployed and nftables enabled in the kernel. +func ReadConfig() (*Config, error) { + stdout, err := execCommand(nil, cmdJSON, cmdList, cmdRuleset) + if err != nil { + return nil, err + } + + config := NewConfig() + if err := config.FromJSON(stdout.Bytes()); err != nil { + return nil, fmt.Errorf("failed to list ruleset: %v", err) + } + + return config, nil +} + +// ApplyConfig applies the given nftables config on the system. +// The system is expected to have the `nft` executable deployed and nftables enabled in the kernel. +func ApplyConfig(c *Config) error { + data, err := c.ToJSON() + if err != nil { + return err + } + + if _, err := execCommand(data, cmdJSON, cmdFile, cmdStdin); err != nil { + return err + } + + return nil +} + +func execCommand(input []byte, args ...string) (*bytes.Buffer, error) { + cmd := exec.Command(cmdBin, args...) + + var stdout, stderr bytes.Buffer + cmd.Stderr = &stderr + cmd.Stdout = &stdout + + if input != nil { + var stdin bytes.Buffer + stdin.Write(input) + cmd.Stdin = &stdin + } + + if err := cmd.Run(); err != nil { + return nil, fmt.Errorf( + "failed to execute %s %s: %v stdin:'%s' stdout:'%s' stderr:'%s'", + cmd.Path, strings.Join(cmd.Args, " "), err, string(input), stdout.String(), stderr.String(), + ) + } + + return &stdout, nil +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/rule.go b/vendor/github.com/networkplumbing/go-nft/nft/rule.go new file mode 100644 index 000000000..673ebcc70 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/rule.go @@ -0,0 +1,128 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package nft + +import ( + "bytes" + "encoding/json" + + "github.com/networkplumbing/go-nft/nft/schema" +) + +// NewRule returns a new schema rule structure. +func NewRule(table *schema.Table, chain *schema.Chain, expr []schema.Statement, handle *int, index *int, comment string) *schema.Rule { + c := &schema.Rule{ + Family: table.Family, + Table: table.Name, + Chain: chain.Name, + Expr: expr, + Handle: handle, + Index: index, + Comment: comment, + } + + return c +} + +// AddRule appends the given rule to the nftable config. +// The rule is added without an explicit action (`add`). +// Adding multiple times the same rule will result in multiple identical rules when applied. +func (c *Config) AddRule(rule *schema.Rule) { + nftable := schema.Nftable{Rule: rule} + c.Nftables = append(c.Nftables, nftable) +} + +// DeleteRule appends a given rule to the nftable config +// with the `delete` action. +// A rule is identified by its handle ID and it must be present in the given rule. +// Attempting to delete a non-existing rule, results with a failure when the config is applied. +// A common usage is to use LookupRule() and then to pass the result to DeleteRule. +func (c *Config) DeleteRule(rule *schema.Rule) { + nftable := schema.Nftable{Delete: &schema.Objects{Rule: rule}} + c.Nftables = append(c.Nftables, nftable) +} + +// LookupRule searches the configuration for a matching rule and returns it. +// The rule is matched first by the table and chain. +// Other matching fields are optional (nil or an empty string arguments imply no-matching). +// Mutating the returned chain will result in mutating the configuration. +func (c *Config) LookupRule(toFind *schema.Rule) []*schema.Rule { + var rules []*schema.Rule + + for _, nftable := range c.Nftables { + if r := nftable.Rule; r != nil { + match := r.Table == toFind.Table && r.Family == toFind.Family && r.Chain == toFind.Chain + if match { + if h := toFind.Handle; h != nil { + match = match && r.Handle != nil && *r.Handle == *h + } + if i := toFind.Index; i != nil { + match = match && r.Index != nil && *r.Index == *i + } + if co := toFind.Comment; co != "" { + match = match && r.Comment == co + } + if toFindStatements := toFind.Expr; toFindStatements != nil { + if match = match && len(toFindStatements) == len(r.Expr); match { + for i, toFindStatement := range toFindStatements { + equal, err := areStatementsEqual(toFindStatement, r.Expr[i]) + match = match && err == nil && equal + } + } + } + if match { + rules = append(rules, r) + } + } + } + } + return rules +} + +func areStatementsEqual(statementA, statementB schema.Statement) (bool, error) { + statementARow, err := json.Marshal(statementA) + if err != nil { + return false, err + } + statementBRow, err := json.Marshal(statementB) + if err != nil { + return false, err + } + return bytes.Equal(statementARow, statementBRow), nil +} + +type RuleIndex int + +// NewRuleIndex returns a rule index object which acts as an iterator. +// When multiple rules are added to a chain, index allows to define an order between them. +// The first rule which is added to a chain should have no index (it is assigned index 0), +// following rules should have the index set, referencing after/before which rule the new one is to be added/inserted. +func NewRuleIndex() *RuleIndex { + var index RuleIndex = -1 + return &index +} + +// Next returns the next iteration value as an integer pointer. +// When first time called, it returns the value 0. +func (i *RuleIndex) Next() *int { + *i++ + var index = int(*i) + return &index +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/schema/chain.go b/vendor/github.com/networkplumbing/go-nft/nft/schema/chain.go new file mode 100644 index 000000000..0fdcdc617 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/schema/chain.go @@ -0,0 +1,53 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package schema + +// Chain Types +const ( + TypeFilter = "filter" + TypeNAT = "nat" + TypeRoute = "route" +) + +// Chain Hooks +const ( + HookPreRouting = "prerouting" + HookInput = "input" + HookOutput = "output" + HookForward = "forward" + HookPostRouting = "postrouting" + HookIngress = "ingress" +) + +// Chain Policies +const ( + PolicyAccept = "accept" + PolicyDrop = "drop" +) + +type Chain struct { + Family string `json:"family"` + Table string `json:"table"` + Name string `json:"name"` + Type string `json:"type,omitempty"` + Hook string `json:"hook,omitempty"` + Prio *int `json:"prio,omitempty"` + Policy string `json:"policy,omitempty"` +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/schema/rule.go b/vendor/github.com/networkplumbing/go-nft/nft/schema/rule.go new file mode 100644 index 000000000..ef86ba3bf --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/schema/rule.go @@ -0,0 +1,260 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package schema + +import ( + "encoding/json" + "fmt" +) + +type Rule struct { + Family string `json:"family"` + Table string `json:"table"` + Chain string `json:"chain"` + Expr []Statement `json:"expr,omitempty"` + Handle *int `json:"handle,omitempty"` + Index *int `json:"index,omitempty"` + Comment string `json:"comment,omitempty"` +} + +type Statement struct { + Match *Match `json:"match,omitempty"` + Verdict +} + +type Verdict struct { + SimpleVerdict + Jump *ToTarget `json:"jump,omitempty"` + Goto *ToTarget `json:"goto,omitempty"` +} + +type SimpleVerdict struct { + Accept bool `json:"-"` + Continue bool `json:"-"` + Drop bool `json:"-"` + Return bool `json:"-"` +} + +type ToTarget struct { + Target string `json:"target"` +} + +type Match struct { + Op string `json:"op"` + Left Expression `json:"left"` + Right Expression `json:"right"` +} + +type Expression struct { + String *string `json:"-"` + Bool *bool `json:"-"` + Float64 *float64 `json:"-"` + Payload *Payload `json:"payload,omitempty"` + // RowData accepts arbitrary data which cannot be composed from the existing schema. + // Use `json.RawMessage()` or `[]byte()` for the value. + // Example: + // `schema.Expression{RowData: json.RawMessage(`{"meta":{"key":"iifname"}}`)}` + RowData json.RawMessage `json:"-"` +} + +type Payload struct { + Protocol string `json:"protocol"` + Field string `json:"field"` +} + +// Verdict Operations +const ( + VerdictAccept = "accept" + VerdictContinue = "continue" + VerdictDrop = "drop" + VerdictReturn = "return" +) + +// Match Operators +const ( + OperAND = "&" // Binary AND + OperOR = "|" // Binary OR + OperXOR = "^" // Binary XOR + OperLSH = "<<" // Left shift + OperRSH = ">>" // Right shift + OperEQ = "==" // Equal + OperNEQ = "!=" // Not equal + OperLS = "<" // Less than + OperGR = ">" // Greater than + OperLSE = "<=" // Less than or equal to + OperGRE = ">=" // Greater than or equal to + OperIN = "in" // Perform a lookup, i.e. test if bits on RHS are contained in LHS value +) + +// Payload Expressions +const ( + PayloadKey = "payload" + // Ethernet + PayloadProtocolEther = "ether" + PayloadFieldEtherDAddr = "daddr" + PayloadFieldEtherSAddr = "saddr" + PayloadFieldEtherType = "type" + + // IP (common) + PayloadFieldIPVer = "version" + PayloadFieldIPDscp = "dscp" + PayloadFieldIPEcn = "ecn" + PayloadFieldIPLen = "length" + PayloadFieldIPSAddr = "saddr" + PayloadFieldIPDAddr = "daddr" + + // IPv4 + PayloadProtocolIP4 = "ip" + PayloadFieldIP4HdrLen = "hdrlength" + PayloadFieldIP4Id = "id" + PayloadFieldIP4FragOff = "frag-off" + PayloadFieldIP4Ttl = "ttl" + PayloadFieldIP4Protocol = "protocol" + PayloadFieldIP4Chksum = "checksum" + + // IPv6 + PayloadProtocolIP6 = "ip6" + PayloadFieldIP6FlowLabel = "flowlabel" + PayloadFieldIP6NextHdr = "nexthdr" + PayloadFieldIP6HopLimit = "hoplimit" +) + +func (s Statement) MarshalJSON() ([]byte, error) { + type _Statement Statement + statement := _Statement(s) + + // Convert to a dynamic structure + data, err := json.Marshal(statement) + if err != nil { + return nil, err + } + dynamicStructure := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &dynamicStructure); err != nil { + return nil, err + } + + switch { + case s.Accept: + dynamicStructure[VerdictAccept] = nil + case s.Continue: + dynamicStructure[VerdictContinue] = nil + case s.Drop: + dynamicStructure[VerdictDrop] = nil + case s.Return: + dynamicStructure[VerdictReturn] = nil + } + + data, err = json.Marshal(dynamicStructure) + if err != nil { + return nil, err + } + return data, nil +} + +func (s *Statement) UnmarshalJSON(data []byte) error { + type _Statement Statement + statement := _Statement{} + + if err := json.Unmarshal(data, &statement); err != nil { + return err + } + *s = Statement(statement) + + dynamicStructure := make(map[string]json.RawMessage) + if err := json.Unmarshal(data, &dynamicStructure); err != nil { + return err + } + _, s.Accept = dynamicStructure[VerdictAccept] + _, s.Continue = dynamicStructure[VerdictContinue] + _, s.Drop = dynamicStructure[VerdictDrop] + _, s.Return = dynamicStructure[VerdictReturn] + + return nil +} + +func (e Expression) MarshalJSON() ([]byte, error) { + var dynamicStruct interface{} + + switch { + case e.RowData != nil: + return e.RowData, nil + case e.String != nil: + dynamicStruct = *e.String + case e.Float64 != nil: + dynamicStruct = *e.Float64 + case e.Bool != nil: + dynamicStruct = *e.Bool + default: + type _Expression Expression + dynamicStruct = _Expression(e) + } + + return json.Marshal(dynamicStruct) +} + +func (e *Expression) UnmarshalJSON(data []byte) error { + var dynamicStruct interface{} + if err := json.Unmarshal(data, &dynamicStruct); err != nil { + return err + } + + switch dynamicStruct.(type) { + case string: + d := dynamicStruct.(string) + e.String = &d + case float64: + d := dynamicStruct.(float64) + e.Float64 = &d + case bool: + d := dynamicStruct.(bool) + e.Bool = &d + case map[string]interface{}: + type _Expression Expression + expression := _Expression(*e) + if err := json.Unmarshal(data, &expression); err != nil { + return err + } + *e = Expression(expression) + default: + return fmt.Errorf("unsupported field type in expression: %T(%v)", dynamicStruct, dynamicStruct) + } + + if e.String == nil && e.Float64 == nil && e.Bool == nil && e.Payload == nil { + e.RowData = data + } + + return nil +} + +func Accept() Verdict { + return Verdict{SimpleVerdict: SimpleVerdict{Accept: true}} +} + +func Continue() Verdict { + return Verdict{SimpleVerdict: SimpleVerdict{Continue: true}} +} + +func Drop() Verdict { + return Verdict{SimpleVerdict: SimpleVerdict{Drop: true}} +} + +func Return() Verdict { + return Verdict{SimpleVerdict: SimpleVerdict{Return: true}} +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/schema/schema.go b/vendor/github.com/networkplumbing/go-nft/nft/schema/schema.go new file mode 100644 index 000000000..45c474faf --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/schema/schema.go @@ -0,0 +1,80 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package schema + +import ( + "encoding/json" +) + +type Root struct { + Nftables []Nftable `json:"nftables"` +} + +const ruleSetKey = "ruleset" + +type Objects struct { + Table *Table `json:"table,omitempty"` + Chain *Chain `json:"chain,omitempty"` + Rule *Rule `json:"rule,omitempty"` + Ruleset bool `json:"-"` +} + +func (o Objects) MarshalJSON() ([]byte, error) { + type _Objects Objects + objects := _Objects(o) + + data, err := json.Marshal(objects) + if err != nil { + return nil, err + } + + if o.Ruleset { + // Convert to a dynamic structure + var dynamicStructure map[string]json.RawMessage + if err := json.Unmarshal(data, &dynamicStructure); err != nil { + return nil, err + } + dynamicStructure[ruleSetKey] = nil + data, err = json.Marshal(dynamicStructure) + if err != nil { + return nil, err + } + } + + return data, nil +} + +type Nftable struct { + Table *Table `json:"table,omitempty"` + Chain *Chain `json:"chain,omitempty"` + Rule *Rule `json:"rule,omitempty"` + + Add *Objects `json:"add,omitempty"` + Delete *Objects `json:"delete,omitempty"` + Flush *Objects `json:"flush,omitempty"` + + Metainfo *Metainfo `json:"metainfo,omitempty"` +} + +type Metainfo struct { + Version string `json:"version"` + ReleaseName string `json:"release_name"` + JsonSchemaVersion int `json:"json_schema_version"` +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/schema/table.go b/vendor/github.com/networkplumbing/go-nft/nft/schema/table.go new file mode 100644 index 000000000..f13d37456 --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/schema/table.go @@ -0,0 +1,35 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package schema + +// Table Address Families +const ( + FamilyIP = "ip" // IPv4 address AddressFamily. + FamilyIP6 = "ip6" // IPv6 address AddressFamily. + FamilyINET = "inet" // Internet (IPv4/IPv6) address AddressFamily. + FamilyARP = "arp" // ARP address AddressFamily, handling IPv4 ARP packets. + FamilyBridge = "bridge" // Bridge address AddressFamily, handling packets which traverse a bridge device. + FamilyNETDEV = "netdev" // Netdev address AddressFamily, handling packets from ingress. +) + +type Table struct { + Family string `json:"family"` + Name string `json:"name"` +} diff --git a/vendor/github.com/networkplumbing/go-nft/nft/table.go b/vendor/github.com/networkplumbing/go-nft/nft/table.go new file mode 100644 index 000000000..7c0ddfe4b --- /dev/null +++ b/vendor/github.com/networkplumbing/go-nft/nft/table.go @@ -0,0 +1,90 @@ +/* + * This file is part of the go-nft project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Copyright 2021 Red Hat, Inc. + * + */ + +package nft + +import "github.com/networkplumbing/go-nft/nft/schema" + +type AddressFamily string + +// Address Families +const ( + FamilyIP AddressFamily = schema.FamilyIP + FamilyIP6 AddressFamily = schema.FamilyIP6 + FamilyINET AddressFamily = schema.FamilyINET + FamilyARP AddressFamily = schema.FamilyARP + FamilyBridge AddressFamily = schema.FamilyBridge + FamilyNETDEV AddressFamily = schema.FamilyNETDEV +) + +type TableAction string + +// Table Actions +const ( + TableADD TableAction = "add" + TableDELETE TableAction = "delete" + TableFLUSH TableAction = "flush" +) + +// NewTable returns a new schema table structure. +func NewTable(name string, family AddressFamily) *schema.Table { + return &schema.Table{ + Name: name, + Family: string(family), + } +} + +// AddTable appends the given table to the nftable config. +// The table is added without an explicit action (`add`). +// Adding multiple times the same table has no effect when the config is applied. +func (c *Config) AddTable(table *schema.Table) { + nftable := schema.Nftable{Table: table} + c.Nftables = append(c.Nftables, nftable) +} + +// DeleteTable appends a given table to the nftable config +// with the `delete` action. +// Attempting to delete a non-existing table, results with a failure when the config is applied. +// All chains and rules under the table are removed as well (when applied). +func (c *Config) DeleteTable(table *schema.Table) { + nftable := schema.Nftable{Delete: &schema.Objects{Table: table}} + c.Nftables = append(c.Nftables, nftable) +} + +// FlushTable appends a given table to the nftable config +// with the `flush` action. +// All chains and rules under the table are removed (when applied). +// Attempting to flush a non-existing table, results with a failure when the config is applied. +func (c *Config) FlushTable(table *schema.Table) { + nftable := schema.Nftable{Flush: &schema.Objects{Table: table}} + c.Nftables = append(c.Nftables, nftable) +} + +// LookupTable searches the configuration for a matching table and returns it. +// Mutating the returned table will result in mutating the configuration. +func (c *Config) LookupTable(toFind *schema.Table) *schema.Table { + for _, nftable := range c.Nftables { + if t := nftable.Table; t != nil { + if t.Name == toFind.Name && t.Family == toFind.Family { + return t + } + } + } + return nil +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 9d66c80c4..2333c559b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -84,6 +84,10 @@ github.com/j-keck/arping # github.com/mattn/go-shellwords v1.0.12 ## explicit github.com/mattn/go-shellwords +# github.com/networkplumbing/go-nft v0.1.1 +## explicit +github.com/networkplumbing/go-nft/nft +github.com/networkplumbing/go-nft/nft/schema # github.com/nxadm/tail v1.4.8 github.com/nxadm/tail github.com/nxadm/tail/ratelimiter