tablomat 1.2.1 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/tablomat/iptables/chain.rb +1 -1
- data/lib/tablomat/iptables/rule.rb +1 -1
- data/lib/tablomat/iptables/table.rb +1 -1
- data/lib/tablomat/iptables.rb +24 -7
- data/lib/tablomat/version.rb +1 -1
- data/spec/ip6tables_spec.rb +148 -0
- data/spec/ipsetip6tables_spec.rb +75 -0
- data/spec/spec_helper.rb +7 -0
- metadata +27 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 173080a9ec9a2627d84f5d4cc6a9db6ae34cde2299411ff7ab8145250f303114
|
4
|
+
data.tar.gz: 7ea6702336b2d690e8c098a1b92af60c22522baaac228a8c4ec3b2c015715290
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 389670b5b1fdec89b2939207cb6eafdad8a6896249ae240a07eb615d5cf1f85cd8f2fa54ee94dbce4471b528b49c8476aaedeb9d073ef6bafa321f177e66d3f7
|
7
|
+
data.tar.gz: '0591296122cfac89e226522a675129ad04b554b6074a6224e2b3de95fdb7deed16569bd8283bb34c164558343a31d0fb29c6013069c2da06453000dc5041fef4'
|
data/lib/tablomat/iptables.rb
CHANGED
@@ -7,17 +7,13 @@ require 'tempfile'
|
|
7
7
|
require 'tablomat/exec'
|
8
8
|
|
9
9
|
module Tablomat
|
10
|
-
# The IPTables interface
|
11
|
-
class
|
10
|
+
# The base class for the IPTables interface
|
11
|
+
class IPTablesBase
|
12
12
|
extend Exec
|
13
13
|
attr_accessor :iptables_bin
|
14
14
|
attr_reader :active, :builtin_chains, :tmp_chain
|
15
15
|
|
16
16
|
def initialize
|
17
|
-
# iptables must be in PATH
|
18
|
-
@iptables_bin = 'iptables'
|
19
|
-
@iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root'
|
20
|
-
|
21
17
|
# get random value for rule creation hack, iptables limits name to 28 characters
|
22
18
|
@tmp_chain_prefix = 'tablomat'
|
23
19
|
|
@@ -25,7 +21,6 @@ module Tablomat
|
|
25
21
|
@tables = {}
|
26
22
|
|
27
23
|
@active = true
|
28
|
-
synchronize
|
29
24
|
end
|
30
25
|
|
31
26
|
def exec(cmd)
|
@@ -199,4 +194,26 @@ module Tablomat
|
|
199
194
|
pp self
|
200
195
|
end
|
201
196
|
end
|
197
|
+
|
198
|
+
# The IPTables interface
|
199
|
+
class IPTables < IPTablesBase
|
200
|
+
def initialize
|
201
|
+
super()
|
202
|
+
# iptables must be in PATH
|
203
|
+
@iptables_bin = 'iptables'
|
204
|
+
@iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root'
|
205
|
+
synchronize
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# The IP6Tables interface
|
210
|
+
class IP6Tables < IPTablesBase
|
211
|
+
def initialize
|
212
|
+
super()
|
213
|
+
# ip6tables must be in PATH
|
214
|
+
@iptables_bin = 'ip6tables'
|
215
|
+
@iptables_bin = "sudo #{@iptables_bin}" if Etc.getlogin != 'root'
|
216
|
+
synchronize
|
217
|
+
end
|
218
|
+
end
|
202
219
|
end
|
data/lib/tablomat/version.rb
CHANGED
@@ -0,0 +1,148 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'etc'
|
5
|
+
|
6
|
+
describe Tablomat::IP6Tables do
|
7
|
+
describe 'Initialize module' do
|
8
|
+
@iptables = Tablomat::IP6Tables.new
|
9
|
+
end
|
10
|
+
|
11
|
+
before(:all) do
|
12
|
+
@command = 'ip6tables'
|
13
|
+
@command = "sudo #{@command}" if Etc.getlogin != 'root'
|
14
|
+
|
15
|
+
@iptables = Tablomat::IP6Tables.new
|
16
|
+
@iptables.activate
|
17
|
+
end
|
18
|
+
|
19
|
+
after(:all) do
|
20
|
+
# cleanup tables
|
21
|
+
@iptables.table('mangle').chain('test', &:apply_delete)
|
22
|
+
@iptables.table('mangle').chain('custom', &:apply_delete)
|
23
|
+
@iptables.table('filter').chain('rspec', &:apply_delete)
|
24
|
+
@iptables.table('nat').chain('rspec', &:apply_delete)
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'does raise an error when getting an invalid ruleset' do
|
28
|
+
output = <<~IPTABLE
|
29
|
+
*nat
|
30
|
+
*mangle
|
31
|
+
:PREROUTING ACCEPT [57:17363]
|
32
|
+
:INPUT ACCEPT [57:17363]
|
33
|
+
:FORWARD ACCEPT [0:0]
|
34
|
+
:OUTPUT ACCEPT [60:16575]
|
35
|
+
:POSTROUTING ACCEPT [60:16575]
|
36
|
+
COMMIT
|
37
|
+
*filter
|
38
|
+
IPTABLE
|
39
|
+
expect { @iptables.parse_output(output) }.to raise_error(StandardError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it 'converts rules to iptables-save format via tmp chain' do
|
43
|
+
expect(@iptables.normalize(protocol: :tcp, sport: 123, source: 'aaaa::ffff')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
|
44
|
+
expect(@iptables.normalize('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
|
45
|
+
expect(@iptables.normalize('-s aaaa::ffff/128 -m tcp --sport 123 -p tcp')).to eq('-s aaaa::ffff/128 -p tcp -m tcp --sport 123')
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'gets current ruleset' do
|
49
|
+
# remove all existing rules in mangle FORWARD (hurts running host rules during test)
|
50
|
+
`#{@command}-save > /tmp/iptables.save`
|
51
|
+
`#{@command} -t mangle -F FORWARD`
|
52
|
+
`#{@command} -t mangle -A FORWARD -p tcp -j ACCEPT`
|
53
|
+
@iptables.synchronize
|
54
|
+
|
55
|
+
`#{@command}-restore < /tmp/iptables.save`
|
56
|
+
# @iptables.print
|
57
|
+
|
58
|
+
# check that filter:INPUT is not owned!
|
59
|
+
expect(@iptables.table('filter').chain('INPUT').owned).to be_falsey
|
60
|
+
|
61
|
+
expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).owned).to be_falsey
|
62
|
+
expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'creates new chain in table mangle' do
|
66
|
+
@iptables.table('mangle').chain('custom').activate
|
67
|
+
expect(@iptables.table('mangle').chain('custom').active).to be_truthy
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'allows to retrieve active rules' do
|
71
|
+
@iptables.append('nat', 'PREROUTING', source: 'cccc::ffff',
|
72
|
+
destination: 'cccc::fffe',
|
73
|
+
protocol: :tcp,
|
74
|
+
dport: 8080,
|
75
|
+
jump: 'DNAT', to: 'cccc::bbbb:9090')
|
76
|
+
target_hash = { source: 'cccc::ffff', destination: 'cccc::fffe', dport: '8080', jump: 'DNAT', protocol: 'tcp', to_dest: 'cccc::bbbb:9090' }
|
77
|
+
rules = @iptables.get_active_rules
|
78
|
+
expect(rules.length).to eq(1)
|
79
|
+
expect(rules.pop).to eq(target_hash)
|
80
|
+
@iptables.delete('nat', 'PREROUTING', source: 'cccc::ffff',
|
81
|
+
destination: 'cccc::fffe',
|
82
|
+
protocol: :tcp,
|
83
|
+
dport: 8080,
|
84
|
+
jump: 'DNAT', to: 'cccc::bbbb:9090')
|
85
|
+
end
|
86
|
+
|
87
|
+
it 'is not possible to set policy for non builtin chains' do
|
88
|
+
expect do
|
89
|
+
@iptables.table('mangle').chain('custom') do |chain|
|
90
|
+
chain.policy 'RETURN'
|
91
|
+
chain.apply
|
92
|
+
end
|
93
|
+
end.to raise_error('Unable to assign policy to non builtin chains, TODO: implement handling')
|
94
|
+
end
|
95
|
+
|
96
|
+
# it "can set policy for builtin chains" do
|
97
|
+
# @iptables.table("mangle").chain("INPUT") do | chain |
|
98
|
+
# chain.set_policy "ACCEPT"
|
99
|
+
# end
|
100
|
+
# @iptables.activate
|
101
|
+
# end
|
102
|
+
|
103
|
+
it 'can remove chains' do
|
104
|
+
# create chain
|
105
|
+
@iptables.table('mangle').chain('test').activate
|
106
|
+
expect(@iptables.exists('mangle', 'test')).to be_truthy
|
107
|
+
@iptables.table('mangle').chain('test').deactivate
|
108
|
+
expect(@iptables.exists('mangle', 'test')).to be_falsey
|
109
|
+
end
|
110
|
+
|
111
|
+
it 'checks if rule already exists' do
|
112
|
+
# check precondition
|
113
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
|
114
|
+
|
115
|
+
# check existing
|
116
|
+
@iptables.append('mangle', 'custom', protocol: :tcp, sport: 123)
|
117
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_truthy
|
118
|
+
expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
|
119
|
+
|
120
|
+
# check missing
|
121
|
+
@iptables.delete('mangle', 'custom', protocol: :tcp, sport: 123)
|
122
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
|
123
|
+
end
|
124
|
+
|
125
|
+
it 'appends new rule via system' do
|
126
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsy
|
127
|
+
@iptables.append('mangle', 'custom', protocol: :tcp, sport: 123)
|
128
|
+
expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).owned).to be_falsey
|
129
|
+
expect(@iptables.table('mangle').chain('FORWARD').rule(@iptables.normalize(protocol: :tcp, jump: :ACCEPT)).active).to be_truthy
|
130
|
+
end
|
131
|
+
|
132
|
+
it 'can remove rules via system' do
|
133
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_truthy
|
134
|
+
@iptables.delete('mangle', 'custom', protocol: :tcp, sport: 123)
|
135
|
+
expect(@iptables.exists('mangle', 'custom', protocol: :tcp, sport: 123)).to be_falsey
|
136
|
+
end
|
137
|
+
|
138
|
+
it 'changes all rules for a specific source address to another source address' do
|
139
|
+
@iptables.append('filter', 'rspec', protocol: :tcp, sport: 123, source: 'aaaa::ffff')
|
140
|
+
@iptables.switch_sources('aaaa::ffff', 'aaaa::bbbb')
|
141
|
+
expect(@iptables.exists('filter', 'rspec', source: 'aaaa::bbbb', protocol: :tcp, sport: 123)).to be_truthy
|
142
|
+
expect(@iptables.exists('filter', 'rspec', source: 'aaaa::ffff', protocol: :tcp, sport: 123)).to be_falsey
|
143
|
+
@iptables.append('nat', 'rspec', protocol: :tcp, sport: 123, source: 'aaaa::ffff', jump: :DNAT, 'to-destination': 'cccc::ffff:123')
|
144
|
+
@iptables.switch_sources('aaaa::ffff', 'aaaa::bbbb')
|
145
|
+
expect(@iptables.exists('nat', 'rspec', source: 'aaaa::bbbb', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': 'cccc::ffff:123')).to be_truthy
|
146
|
+
expect(@iptables.exists('nat', 'rspec', source: 'aaaa::ffff', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': 'cccc::ffff:123')).to be_falsey
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'etc'
|
5
|
+
|
6
|
+
describe Tablomat::IP6Tables, Tablomat::IPSet do
|
7
|
+
describe 'Initialize module' do
|
8
|
+
@ipset = Tablomat::IPSet.new
|
9
|
+
@iptables = Tablomat::IP6Tables.new
|
10
|
+
end
|
11
|
+
|
12
|
+
before(:all) do
|
13
|
+
@command = 'ipset'
|
14
|
+
@command = "sudo #{@command}" if Etc.getlogin != 'root'
|
15
|
+
@ipset = Tablomat::IPSet.new
|
16
|
+
@iptables = Tablomat::IP6Tables.new
|
17
|
+
@iptables.activate
|
18
|
+
end
|
19
|
+
|
20
|
+
after(:all) do
|
21
|
+
# cleanup table and ipsets
|
22
|
+
@iptables.table('mangle').chain('custom', &:apply_delete)
|
23
|
+
@ipset.destroy_all
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'can create and delete rules using ipsets if set exists' do
|
27
|
+
# initialize ipset and create set
|
28
|
+
@ipset.create(set_name: 'testset1', type: 'hash:ip', create_options: 'family inet6')
|
29
|
+
# create rule
|
30
|
+
@iptables.append('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)
|
31
|
+
expect(@iptables.exists('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
|
32
|
+
@iptables.delete('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)
|
33
|
+
expect(@iptables.exists('mangle', 'custom', set: @ipset.matchset(set_name: 'testset1', flags: 'src'), protocol: :tcp, sport: 123)).to be_falsey
|
34
|
+
end
|
35
|
+
|
36
|
+
it 'can create and delete rules using ipsets with Options' do
|
37
|
+
@iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)
|
38
|
+
expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)).to be_truthy
|
39
|
+
@iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)
|
40
|
+
expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset1', flags: 'src', option: '--update-subcounters', negate_option: true, negate: true), protocol: :tcp, sport: 123)).to be_falsey
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'can not create rules using a set if the set does not exist' do
|
44
|
+
expect(@ipset.set('testset2').exists?).to be_falsey
|
45
|
+
expect { @iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset', flags: 'src'), protocol: :tcp, sport: 123) }.to raise_error(RuntimeError)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'can not destroy a set if a rule is using it' do
|
49
|
+
@ipset.create(set_name: 'testset3', type: 'hash:ip', create_options: 'family inet6')
|
50
|
+
@iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)
|
51
|
+
expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)).to be_truthy
|
52
|
+
expect { @ipset.destroy(set_name: 'testset3') }.to raise_error(Tablomat::IPSetError)
|
53
|
+
@iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)
|
54
|
+
expect(@iptables.exists('mangle', 'custom', match: @ipset.matchset(set_name: 'testset3', flags: 'src'), protocol: :tcp, sport: 123)).to be_falsey
|
55
|
+
@ipset.destroy(set_name: 'testset3')
|
56
|
+
end
|
57
|
+
|
58
|
+
it 'lists the set in active rules, if a set is given' do
|
59
|
+
@ipset.create(set_name: 'testset4', type: 'hash:ip', create_options: 'family inet6')
|
60
|
+
@iptables.append('mangle', 'custom', match: @ipset.matchset(set_name: 'testset4', flags: 'src'), protocol: :tcp, sport: 123)
|
61
|
+
active_rules = @iptables.get_active_rules('mangle', 'custom')
|
62
|
+
expect(active_rules[-1][:match]).to eq('testset4')
|
63
|
+
@iptables.delete('mangle', 'custom', match: @ipset.matchset(set_name: 'testset4', flags: 'src'), protocol: :tcp, sport: 123)
|
64
|
+
@ipset.destroy(set_name: 'testset4')
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'does not list a set in active rules, if no set is given' do
|
68
|
+
@ipset.create(set_name: 'testset5', type: 'hash:ip', create_options: 'family inet6')
|
69
|
+
@iptables.append('mangle', 'custom', source: 'aaaa::ffff', protocol: :tcp, sport: 123)
|
70
|
+
active_rules = @iptables.get_active_rules('mangle', 'custom')
|
71
|
+
expect(active_rules[-1].key?(:match)).to be_falsey
|
72
|
+
@iptables.delete('mangle', 'custom', source: 'aaaa::ffff', protocol: :tcp, sport: 123)
|
73
|
+
@ipset.destroy(set_name: 'testset5')
|
74
|
+
end
|
75
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'simplecov'
|
4
|
+
require 'simplecov-cobertura'
|
5
|
+
|
6
|
+
SimpleCov.formatters = [
|
7
|
+
SimpleCov::Formatter::CoberturaFormatter,
|
8
|
+
SimpleCov::Formatter::HTMLFormatter
|
9
|
+
]
|
10
|
+
|
4
11
|
SimpleCov.start do
|
5
12
|
enable_coverage :branch
|
6
13
|
add_filter '/spec/'
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tablomat
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Matthias Wübbeling
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date:
|
12
|
+
date: 2022-11-03 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
@@ -81,6 +81,26 @@ dependencies:
|
|
81
81
|
- - "~>"
|
82
82
|
- !ruby/object:Gem::Version
|
83
83
|
version: 0.18.5
|
84
|
+
- !ruby/object:Gem::Dependency
|
85
|
+
name: simplecov-cobertura
|
86
|
+
requirement: !ruby/object:Gem::Requirement
|
87
|
+
requirements:
|
88
|
+
- - "~>"
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: '1.4'
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: 1.4.1
|
94
|
+
type: :development
|
95
|
+
prerelease: false
|
96
|
+
version_requirements: !ruby/object:Gem::Requirement
|
97
|
+
requirements:
|
98
|
+
- - "~>"
|
99
|
+
- !ruby/object:Gem::Version
|
100
|
+
version: '1.4'
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: 1.4.1
|
84
104
|
description: A simple wrapper for iptables
|
85
105
|
email:
|
86
106
|
- matthias.wuebbeling@cs.uni-bonn.de
|
@@ -99,7 +119,9 @@ files:
|
|
99
119
|
- lib/tablomat/iptables/rule.rb
|
100
120
|
- lib/tablomat/iptables/table.rb
|
101
121
|
- lib/tablomat/version.rb
|
122
|
+
- spec/ip6tables_spec.rb
|
102
123
|
- spec/ipset_spec.rb
|
124
|
+
- spec/ipsetip6tables_spec.rb
|
103
125
|
- spec/ipsetiptables_spec.rb
|
104
126
|
- spec/iptables_spec.rb
|
105
127
|
- spec/spec_helper.rb
|
@@ -122,12 +144,14 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
144
|
- !ruby/object:Gem::Version
|
123
145
|
version: '0'
|
124
146
|
requirements: []
|
125
|
-
rubygems_version: 3.
|
147
|
+
rubygems_version: 3.3.16
|
126
148
|
signing_key:
|
127
149
|
specification_version: 4
|
128
150
|
summary: This wrapper provides an interface to iptables.
|
129
151
|
test_files:
|
152
|
+
- spec/ip6tables_spec.rb
|
130
153
|
- spec/ipset_spec.rb
|
154
|
+
- spec/ipsetip6tables_spec.rb
|
131
155
|
- spec/ipsetiptables_spec.rb
|
132
156
|
- spec/iptables_spec.rb
|
133
157
|
- spec/spec_helper.rb
|