tablomat 1.2.1 → 1.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|