Skip to content

Commit e7f5977

Browse files
authored
Add nftables support (#16)
* Update Logs Fix ipv4 regex * Add nftables support * Add example
1 parent d449664 commit e7f5977

19 files changed

+1242
-77
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ You may use single-dc configuration, specify it's name as *dc* though.
4040
3. Change default [**iptables template**](samples/iptables.rules)
4141
## Service
4242
4. Install & launch befw-firewalld service on every agent node
43-
5. Edit [sample configuration](samples/befw.conf) and place it to /etc/befw.conf
43+
5. Edit [sample configuration](samples/befw.conf) and place it to /etc/befw.conf. Options:
44+
- firewall (values: "nft"|"nftc7", default: "") - Change firewall provider: nft - nftables, nftc7 - legacy nftables 0.8 (centos7), otherwise - iptables/ipset
4445
6. Place [sample service](samples/service.json) to *services dir* and see what will happen
4546
## Puppet/Hiera/PuppetDB
4647
*We use puppet to do items 3-6 and below as we have a huge puppet installation. Skip this if you don't.*

befw/config.go

+17-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,10 @@ import (
2525
)
2626

2727
// Current firewall instance
28-
var fw firewall = newIPTables() // Can be configured
28+
var fw firewall = newIPTables() // Default. Will be reconfigured
29+
30+
const cfg_fw_nftc7 = "nftc7"
31+
const cfg_fw_nft = "nft"
2932

3033
var OverrideConfig = make(map[string]string)
3134

@@ -97,6 +100,19 @@ func createConfig(configFile string) *config {
97100
setConfigKVSeconds(&ret.Timeout.ConsulWatch, "consulwatch_timeout_sec", OverrideConfig, kv)
98101
setConfigKVBool(&ret.NIDSEnable, "nids", OverrideConfig, kv)
99102

103+
// Get firewall provider
104+
if val, ok := kv["firewall"]; ok {
105+
if val == cfg_fw_nftc7 {
106+
logging.LogInfo("Use nftables (nftc7 - for centos7) firewall")
107+
fw = NewNFTc7()
108+
} else if val == cfg_fw_nft {
109+
logging.LogInfo("Use nftables (nft) firewall")
110+
fw = NewNFT()
111+
} else {
112+
logging.LogInfo("Use iptables/ipset firewall")
113+
}
114+
}
115+
100116
if _, ok := kv["fail"]; ok {
101117
logging.LogError("[Config] you must edit your Config file before proceed")
102118
}

befw/fwiptables.go

+16-9
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,14 @@ import (
1919
"encoding/json"
2020
"errors"
2121
"fmt"
22-
"github.com/wgnet/befw/logging"
2322
"io/ioutil"
2423
"net"
2524
"strconv"
2625
"strings"
2726
"sync"
2827
"time"
28+
29+
"github.com/wgnet/befw/logging"
2930
)
3031

3132
// Implementation of REAL calls of iptables, ip6tables, ipset
@@ -47,7 +48,7 @@ type fwIPTables struct {
4748
type binIPTables interface {
4849
// ipset
4950
ipsetList(name string) (string, error)
50-
ipsetRestore(name, rule string) error
51+
ipsetRestore(name string, rule string) error
5152
// iptables
5253
iptablesList() (string, error)
5354
iptablesRestore(rules string) error
@@ -134,6 +135,7 @@ func (fw *fwIPTables) KeepConsistent() error {
134135

135136
// Apply state
136137
func (fw *fwIPTables) Apply(state *state) error {
138+
logging.LogDebug("Apply state.")
137139
fw.lock.Lock()
138140
defer fw.lock.Unlock()
139141
logging.LogDebug("Try to apply state")
@@ -214,7 +216,7 @@ func (fw *fwIPTables) rulesGenerate(state *state, applied map[string][]string, i
214216
if ip6 {
215217
name += V6
216218
}
217-
name = correctIPSetName(name)
219+
name = correctIPSetName(name, 31)
218220
strings.NewReplacer("{NAME}", name,
219221
"{PRIORITY}", strconv.Itoa(set.Priority),
220222
"{TARGET}", set.Target,
@@ -230,7 +232,7 @@ func (fw *fwIPTables) rulesGenerate(state *state, applied map[string][]string, i
230232
if ip6 {
231233
name += V6
232234
}
233-
name = correctIPSetName(name)
235+
name = correctIPSetName(name, 31)
234236
// Check template by service mode.
235237
var templateRule string = templates.Line
236238
if srv.Mode == MODE_ENFORCING {
@@ -272,8 +274,8 @@ func (fw *fwIPTables) rulesGenerate(state *state, applied map[string][]string, i
272274
// Generate text rules for 'ipset' command
273275
func (fw *fwIPTables) ipsetGenerate(name string, set []string) string {
274276
result := new(strings.Builder)
275-
name6 := correctIPSetName(name + V6)
276-
name = correctIPSetName(name)
277+
name6 := correctIPSetName(name+V6, 31)
278+
name = correctIPSetName(name, 31)
277279
tmp := fmt.Sprintf("tmp_%s", getRandomString())
278280
tmp6 := fmt.Sprintf("tmp6_%s", getRandomString())
279281

@@ -288,8 +290,12 @@ func (fw *fwIPTables) ipsetGenerate(name string, set []string) string {
288290
// 2. Fill tmp ipset
289291
for _, cidr := range set {
290292
_, nt, e := net.ParseCIDR(cidr)
291-
if e != nil || nt == nil {
292-
logging.LogWarning("[IPT] Skip set. Can't parse CIDR: ", cidr)
293+
if e != nil {
294+
logging.LogWarning("[IPT] Skip set. Can't parse as CIDR: ", cidr)
295+
continue
296+
}
297+
if nt == nil {
298+
logging.LogWarning("[IPT] Skip set. No CIDR mask: ", cidr)
293299
continue
294300
}
295301
if isIPv6(cidr) {
@@ -323,7 +329,7 @@ func (fw *fwIPTables) ipsetGenerate(name string, set []string) string {
323329
func (this *config) newRules() *IptablesRules {
324330
rules := defaultRules()
325331
if data, e := ioutil.ReadFile(this.RulesPath); e != nil {
326-
logging.LogWarning("[IPT] Can't read", this.RulesPath, "; using default:", e.Error())
332+
logging.LogWarning("[IPT] Can't read ", this.RulesPath, "; using default: ", e.Error())
327333
} else {
328334
if e := json.Unmarshal(data, rules); e != nil {
329335
logging.LogWarning("[IPT] Can't parse", this.RulesPath, "; using default:", e.Error())
@@ -486,6 +492,7 @@ func (fw *fwIPTables) rules6Restore() error {
486492

487493
// Return state of ipset
488494
func (b *binRealIPTables) ipsetList(name string) (string, error) {
495+
name = correctIPSetName(name, 31)
489496
out, err := run(nil, "ipset", "-o", "save", "list", name)
490497
if err != nil {
491498
logging.LogWarning("[IPT] Failed to get list of ipset:", name, out)

befw/fwiptables_test.go

+27-15
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ type mockIPTables struct {
4040
}
4141

4242
func TestKeepConsistent(t *testing.T) {
43-
s, fw, mock := dummyState()
43+
s := testDummyState()
44+
fw, mock := mockFirewallIPTables()
4445

4546
// Initialization: Apply generated rules
4647
fw.Apply(&s)
@@ -109,7 +110,8 @@ func TestKeepConsistent(t *testing.T) {
109110
}
110111

111112
func TestApply(t *testing.T) {
112-
s, fw, _ := dummyState()
113+
s := testDummyState()
114+
fw, _ := mockFirewallIPTables()
113115
fw.Apply(&s)
114116

115117
// Expect patterns in output
@@ -153,7 +155,7 @@ func TestApply(t *testing.T) {
153155
"swap tmp_[a-zA-Z0-9]* C",
154156
}, t)
155157

156-
// Unexpect pattrins in ipset output
158+
// Unexpect patterns in ipset output
157159
unexpects(ipsetResult, []string{"add tmp_[a-zA-Z0-9]* 42\\.2\\.3\\.4",
158160
"add tmp_[a-zA-Z0-9]* 5\\.1\\.",
159161
}, t)
@@ -181,7 +183,8 @@ func TestApply(t *testing.T) {
181183
}
182184

183185
func TestRulesGenerate(t *testing.T) {
184-
s, fw, _ := dummyState()
186+
s := testDummyState()
187+
fw, _ := mockFirewallIPTables()
185188
test := fw.rulesGenerate(&s, s.StaticIPSets, false)
186189

187190
// Expect patterns in output
@@ -198,7 +201,8 @@ func TestRulesGenerate(t *testing.T) {
198201
}
199202

200203
func TestIpsetGenerateServices(t *testing.T) {
201-
s, fw, _ := dummyState()
204+
s := testDummyState()
205+
fw, _ := mockFirewallIPTables()
202206

203207
var result string = ""
204208
for _, srv := range s.NodeServices {
@@ -220,7 +224,8 @@ func TestIpsetGenerateServices(t *testing.T) {
220224
}
221225

222226
func TestIpsetGenerateStaticSetList(t *testing.T) {
223-
s, fw, _ := dummyState()
227+
s := testDummyState()
228+
fw, _ := mockFirewallIPTables()
224229
var result string = ""
225230
for name, set := range s.StaticIPSets {
226231
result += fw.ipsetGenerate(name, set)
@@ -235,7 +240,8 @@ func TestIpsetGenerateStaticSetList(t *testing.T) {
235240
}
236241

237242
func TestIpsetGenerateConstSetList(t *testing.T) {
238-
s, fw, _ := dummyState()
243+
s := testDummyState()
244+
fw, _ := mockFirewallIPTables()
239245

240246
// Check expectations
241247
expects := []string{"swap tmp_", "destroy tmp_", "create tmp_"}
@@ -255,7 +261,8 @@ func TestIpsetGenerateConstSetList(t *testing.T) {
255261

256262
// Test IPv6 support (ipset)
257263
func TestIpsetV6(t *testing.T) {
258-
s, fw, _ := dummyState()
264+
s := testDummyState()
265+
fw, _ := mockFirewallIPTables()
259266
name := "TestService"
260267
srv6 := asService(name, []string{"22/tcp"}, []string{"4.4.4.4/28", "::1:5ee:bad:c0de/80"})
261268
s.NodeServices = append(s.NodeServices, srv6)
@@ -290,7 +297,8 @@ func TestIpsetV6(t *testing.T) {
290297
}
291298

292299
func TestIptablesV6(t *testing.T) {
293-
s, fw, _ := dummyState()
300+
s := testDummyState()
301+
fw, _ := mockFirewallIPTables()
294302

295303
// Exist v6
296304
name := "TestService"
@@ -310,25 +318,29 @@ func TestIptablesV6(t *testing.T) {
310318

311319
// ==========[ Util Func ]==========
312320

313-
func dummyState() (state, *fwIPTables, *mockIPTables) {
321+
func testDummyState() state {
314322
// State:
315323
s := state{
316324
StaticIPSets: map[string][]string{
317325
"A": []string{"1.2.3.1/32", "0.0.0.0/0", "10.10.10.10/28", "192.168.1.1/32", "42.2.3.4", "::0/0"},
318326
"B": []string{"1.2.3.2/32", "0.0.0.0/0", "10.10.10.10/28", "192.168.1.1/32", "42.2.3.4"},
319327
},
320328
NodeServices: []bService{
321-
asService("B", []string{"10/tcp", "8080:8090"}, []string{"1.2.3.3/32", "0.0.0.0/0", "10.0.0.0/12", "5.1.1.3/32", "42.2.3.4"}),
329+
asService("B", []string{"10/tcp", "8080:8090", "443/tcp", "443/tcp"}, []string{"1.2.3.3/32", "0.0.0.0/0", "10.0.0.0/12", "5.1.1.3/32", "42.2.3.4"}),
322330
asService("C", []string{"20/tcp", "8085:9090"}, []string{"1.2.3.4/32", "10.0.0.0/12", "1.2.3.6/32", "43.2.3.4", "5.1.1.4/32"}),
323-
asService("D", []string{"30/tcp"}, []string{"1.2.3.4/32", "10.0.0.0/12", "1.2.3.6/32", "43.2.3.4", "5.1.1.4/32"}),
324-
asService("E", []string{}, []string{"1.2.3.4/32", "10.0.0.0/12", "1.2.3.6/32", "43.2.3.4", "5.1.1.4/32"}),
331+
asService("test.srv2.stg-hiera_tcp_19080", []string{"30/tcp"}, []string{"1.2.3.4/32", "10.0.0.0/12", "1.2.3.6/32", "43.2.3.4", "5.1.1.4/32"}),
332+
asService("ThiS_is_very_and_Very_LONG_service", []string{}, []string{"1.2.3.4/32", "10.0.0.0/12", "1.2.3.6/32", "43.2.3.4", "5.1.1.4/32"}),
325333
},
326334
}
327335
s.Config = &config{
328336
StaticSetList: staticIPSetList,
329337
}
330338
s.fillMandatoryIPSet()
331339

340+
return s
341+
}
342+
343+
func mockFirewallIPTables() (*fwIPTables, *mockIPTables) {
332344
// Firewall:
333345
var fw *fwIPTables = newIPTables()
334346
mock := mockIPTables{
@@ -339,7 +351,7 @@ func dummyState() (state, *fwIPTables, *mockIPTables) {
339351

340352
// ipset
341353
mock.mockIpsetList = func(name string) (string, error) { return mock.ipsets[name], nil }
342-
mock.mockIpsetRestore = func(name, rules string) error { mock.ipsets[name] = rules; return nil }
354+
mock.mockIpsetRestore = func(name string, rules string) error { mock.ipsets[name] = rules; return nil }
343355
// iptables
344356
mock.mockIptablesList = func() (string, error) { return mock.rules, nil }
345357
mock.mockIptablesRestore = func(rules string) error { mock.rules = rules; return nil }
@@ -348,7 +360,7 @@ func dummyState() (state, *fwIPTables, *mockIPTables) {
348360
mock.mockIp6tablesRestore = func(rules string) error { mock.rules6 = rules; return nil }
349361

350362
fw.bin = &mock
351-
return s, fw, &mock
363+
return fw, &mock
352364
}
353365

354366
func asService(name string, ports []string, clients []string) bService {

0 commit comments

Comments
 (0)