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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3acd84433060bbc3bf7e1502c05f0e9889694d3a32037e22e15f743ca01b5fc3
4
- data.tar.gz: 9dd4eaab8169d4a12bc8d84de57913f35d8ffc0d90d9f716e12a801f2936e4aa
3
+ metadata.gz: 173080a9ec9a2627d84f5d4cc6a9db6ae34cde2299411ff7ab8145250f303114
4
+ data.tar.gz: 7ea6702336b2d690e8c098a1b92af60c22522baaac228a8c4ec3b2c015715290
5
5
  SHA512:
6
- metadata.gz: 25075f75a86af90044a997409afec220443610589c68bba8d22144586724288ae860b5dcfb191ba64afaf14b420c8cfc838bc6e7f3b887f21a07a2ac99862c7e
7
- data.tar.gz: c8be92592b3fe8e3c67439db72aa7f1717da3c13ed32b412009a65ebeb4d9bdbbb291c3391a2dda28e684cefb758b9be79960f64d0bc7183e1279deebfbf62bc
6
+ metadata.gz: 389670b5b1fdec89b2939207cb6eafdad8a6896249ae240a07eb615d5cf1f85cd8f2fa54ee94dbce4471b528b49c8476aaedeb9d073ef6bafa321f177e66d3f7
7
+ data.tar.gz: '0591296122cfac89e226522a675129ad04b554b6074a6224e2b3de95fdb7deed16569bd8283bb34c164558343a31d0fb29c6013069c2da06453000dc5041fef4'
@@ -3,7 +3,7 @@
3
3
  require 'tablomat/iptables/rule'
4
4
 
5
5
  module Tablomat
6
- class IPTables
6
+ class IPTablesBase
7
7
  # The IPTables class is the interface to the iptables command
8
8
  class Chain
9
9
  attr_accessor :owned
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tablomat
4
- class IPTables
4
+ class IPTablesBase
5
5
  # IPTables are made of Rules
6
6
  class Rule
7
7
  attr_accessor :owned, :active, :method, :position
@@ -3,7 +3,7 @@
3
3
  require 'tablomat/iptables/chain'
4
4
 
5
5
  module Tablomat
6
- class IPTables
6
+ class IPTablesBase
7
7
  # Puts the Table in IPTables
8
8
  class Table
9
9
  attr_accessor :owned
@@ -7,17 +7,13 @@ require 'tempfile'
7
7
  require 'tablomat/exec'
8
8
 
9
9
  module Tablomat
10
- # The IPTables interface
11
- class IPTables
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Tablomat
4
- VERSION = '1.2.1'
4
+ VERSION = '1.3.0'
5
5
  end
@@ -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.2.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: 2021-03-12 00:00:00.000000000 Z
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.0.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