tablomat 1.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3acd84433060bbc3bf7e1502c05f0e9889694d3a32037e22e15f743ca01b5fc3
4
+ data.tar.gz: 9dd4eaab8169d4a12bc8d84de57913f35d8ffc0d90d9f716e12a801f2936e4aa
5
+ SHA512:
6
+ metadata.gz: 25075f75a86af90044a997409afec220443610589c68bba8d22144586724288ae860b5dcfb191ba64afaf14b420c8cfc838bc6e7f3b887f21a07a2ac99862c7e
7
+ data.tar.gz: c8be92592b3fe8e3c67439db72aa7f1717da3c13ed32b412009a65ebeb4d9bdbbb291c3391a2dda28e684cefb758b9be79960f64d0bc7183e1279deebfbf62bc
data/lib/tablomat.rb ADDED
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tablomat/version'
4
+ require 'tablomat/iptables'
5
+ require 'tablomat/ipset'
6
+
7
+ # # module Tablomat
8
+ # module Tablomat
9
+ # end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tempfile'
4
+ require 'English'
5
+
6
+ # Module defined in exec.rb File to execute Commands
7
+ module Exec
8
+ def self.exec(cmd)
9
+ errfile = Tempfile.new('tablomat')
10
+ stdout = `#{cmd} 2> #{errfile.path}`
11
+ unless $CHILD_STATUS.success?
12
+ err = errfile.read
13
+ errfile.close
14
+ errfile.unlink
15
+ raise err.to_s
16
+ end
17
+ stdout
18
+ end
19
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tablomat/ipset/set'
4
+ require 'tablomat/exec'
5
+
6
+ module Tablomat
7
+ # Errorclass for IPSet-Errors
8
+ class IPSetError < StandardError; end
9
+
10
+ # The IPSet interface
11
+ class IPSet
12
+ extend Exec
13
+ attr_accessor :ipset_bin
14
+
15
+ def initialize
16
+ @ipset_bin = 'ipset'
17
+ @ipset_bin = "sudo #{@ipset_bin}" if Etc.getlogin != 'root'
18
+ @sets = {}
19
+ end
20
+
21
+ def matchset(flags:, option: '', negate: false, negate_option: false, set_name:)
22
+ set_set = "--match-set #{set_name} #{flags}"
23
+ set_set = "! #{set_set}" if negate
24
+ option = "! #{option}" if negate_option
25
+ "set #{set_set} #{option}"
26
+ end
27
+
28
+ def set(name, &block)
29
+ name = name.to_s.downcase
30
+ (@sets[name] || Set.new(self, name)).tap do |set|
31
+ @sets[name] = set
32
+ block&.call(set)
33
+ end
34
+ @sets[name]
35
+ end
36
+
37
+ def exec(cmd)
38
+ Exec.exec(cmd)
39
+ rescue StandardError => e
40
+ raise IPSetError.new, e.message
41
+ end
42
+
43
+ def destroy_all(really_all = false)
44
+ # destroys all sets created in this Instance unless really_all = true
45
+ if really_all
46
+ command = "#{@ipset_bin} destroy"
47
+ exec(command)
48
+ else
49
+ @sets.each do |_name, set|
50
+ set.destroy if set.active
51
+ end
52
+ end
53
+ end
54
+
55
+ def add(set_name:, entry_data:, add_options: '', exist: false)
56
+ set(set_name).add(entry_data: entry_data, add_options: add_options, exist: exist)
57
+ end
58
+
59
+ def create(set_name:, type:, create_options: '', rangefrom: '', rangeto: '')
60
+ set(set_name).create(type: type, create_options: create_options, rangefrom: rangefrom, rangeto: rangeto)
61
+ end
62
+
63
+ def del(set_name:, entry_data:, exist: false)
64
+ set(set_name).del(entry_data: entry_data, exist: exist)
65
+ end
66
+
67
+ def destroy(set_name:)
68
+ set(set_name).destroy
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tablomat
4
+ class IPSet
5
+ # interface to manage the entrys inside the sets
6
+ class Entry
7
+ def initialize(set, description)
8
+ @system = set.system
9
+ @set = set
10
+ @description = description
11
+ end
12
+
13
+ def exists?
14
+ command = "#{@system.ipset_bin} del #{@set.name} #{@description}"
15
+ @system.exec command
16
+ command = "#{@system.ipset_bin} add #{@set.name} #{@description}"
17
+ @system.exec command
18
+ true
19
+ rescue IPSetError => e
20
+ raise unless e.message.include?("Element cannot be deleted from the set: it's not added")
21
+
22
+ false
23
+ end
24
+
25
+ def add(add_options, exist = false)
26
+ command = "#{@system.ipset_bin} add #{@set.name} #{@description} #{add_options}"
27
+ command = "#{command} -!" if exist
28
+ @system.exec command
29
+ end
30
+
31
+ def del(exist = false)
32
+ command = "#{@system.ipset_bin} del #{@set.name} #{@description}"
33
+ command = "#{command} -!" if exist
34
+ @system.exec command
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tablomat/ipset/entry'
4
+
5
+ module Tablomat
6
+ class IPSet
7
+ # Interface to manage ipsets
8
+ class Set
9
+ attr_reader :name, :system, :active
10
+
11
+ def initialize(system, name)
12
+ @system = system
13
+ @name = name
14
+ @entrys = {}
15
+ @active = false
16
+ end
17
+
18
+ def exists?
19
+ command = "#{@system.ipset_bin} list #{@name}"
20
+ @system.exec command
21
+ true
22
+ rescue IPSetError => e
23
+ raise unless e.message.include?('The set with the given name does not exist')
24
+
25
+ false
26
+ end
27
+
28
+ def type
29
+ command = "#{system.ipset_bin} list #{@name}"
30
+ stdout = `#{command} 2>&1`.strip << "\n"
31
+ if $CHILD_STATUS != 0
32
+ # throw error
33
+ puts "Invalid return value when calling #{command}"
34
+ end
35
+ stdout = stdout.split("\n").select { |s| s.include?('Type:') }[0]
36
+ stdout.split(/Type: /)[1]
37
+ rescue StandardError => e
38
+ puts "[error] #{e}"
39
+ end
40
+
41
+ def entry(data, &block)
42
+ data = data.to_s.downcase
43
+ (@entrys[data] || Entry.new(self, data)).tap do |entry|
44
+ @entrys[data] = entry
45
+ block&.call(entry)
46
+ end
47
+ end
48
+
49
+ def add(entry_data:, add_options: '', exist: false)
50
+ entry(entry_data).add(add_options, exist)
51
+ end
52
+
53
+ def del(entry_data:, exist: false)
54
+ entry(entry_data).del(exist)
55
+ end
56
+
57
+ def create(type:, create_options: '', rangefrom: '', rangeto: '')
58
+ create_options = "range #{rangefrom}-#{rangeto} #{create_options}" if type.include?('bitmap')
59
+ command = "#{@system.ipset_bin} create #{@name} #{type} #{create_options}"
60
+ @system.exec command
61
+ @active = true
62
+ end
63
+
64
+ def destroy
65
+ command = "#{@system.ipset_bin} destroy #{@name}"
66
+ @system.exec command
67
+ @active = false
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,202 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'English'
4
+ require 'tablomat/iptables/table'
5
+ require 'digest'
6
+ require 'tempfile'
7
+ require 'tablomat/exec'
8
+
9
+ module Tablomat
10
+ # The IPTables interface
11
+ class IPTables
12
+ extend Exec
13
+ attr_accessor :iptables_bin
14
+ attr_reader :active, :builtin_chains, :tmp_chain
15
+
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
+ # get random value for rule creation hack, iptables limits name to 28 characters
22
+ @tmp_chain_prefix = 'tablomat'
23
+
24
+ @builtin_chains = { filter: %w[INPUT FORWARD OUTPUT], nat: %w[PREROUTING INPUT OUTPUT POSTROUTING], mangle: %w[PREROUTING INPUT FORWARD OUTPUT POSTROUTING] }
25
+ @tables = {}
26
+
27
+ @active = true
28
+ synchronize
29
+ end
30
+
31
+ def exec(cmd)
32
+ Exec.exec(cmd)
33
+ end
34
+
35
+ def synchronize
36
+ # called regularly by normalize
37
+ command = "#{@iptables_bin}-save"
38
+ stdout = `#{command} 2>&1`.strip << "\n"
39
+ if $CHILD_STATUS != 0
40
+ # throw error
41
+ puts "Invalid return value when calling #{command}"
42
+ end
43
+ parse_output stdout
44
+ rescue StandardError => e
45
+ puts "[error] #{e}"
46
+ end
47
+
48
+ # rubocop:disable Metrics/AbcSize
49
+ def parse_output(stdout)
50
+ stdout = stdout.split("\n").reject { |s| s[0] == '#' }.join("\n")
51
+ data = {}
52
+ stdout.split(/^\*/).reject { |s| s == '' }.each do |ruleset|
53
+ table, rule = ruleset.match(/(.+?)\n(.*)/m).to_a[1..-1]
54
+ data[table] = {}
55
+
56
+ raise "Empty ruleset in table #{table}" if table.nil?
57
+
58
+ rule.scan(/^:(.+?)\s+/).each do |match|
59
+ data[table][match[0]] = []
60
+ end
61
+
62
+ rule.scan(/^-A (.+?) (.+?)\n/).each do |match|
63
+ data[table][match[0]] << match[1]
64
+ end
65
+ end
66
+ parse_data(data)
67
+ end
68
+ # rubocop:enable Metrics/AbcSize
69
+
70
+ def parse_data(data)
71
+ data.each do |table, chains|
72
+ t = self.table(table, false)
73
+ t.activate true
74
+ parse_table(t, chains)
75
+ end
76
+ end
77
+
78
+ def parse_table(table, chains)
79
+ chains.each do |chain, rules|
80
+ c = table.chain(chain, false)
81
+ c.activate true
82
+ parse_chain(c, rules)
83
+ end
84
+ end
85
+
86
+ def parse_chain(chain, rules)
87
+ rules.each do |rule|
88
+ r = chain.rule(rule, false)
89
+ r.activate true
90
+ end
91
+ end
92
+
93
+ def table(name, owned = true, &block)
94
+ name = name.to_s.downcase
95
+ (@tables[name] || Table.new(self, name, owned)).tap do |table|
96
+ @tables[name] = table
97
+ block&.call(table)
98
+ end
99
+ end
100
+
101
+ # converts any given rule to the iptables internal description format by using a temporary table
102
+ # rubocop:disable Metrics/AbcSize
103
+ def normalize(data, table = 'filter')
104
+ synchronize
105
+ tmp_chain = "#{@tmp_chain_prefix}#{Digest::SHA256.hexdigest Random.rand(1024).to_s}"[0, 28]
106
+ self.table(table).chain(tmp_chain).activate
107
+ self.table(table).append(tmp_chain, data)
108
+ synchronize
109
+ normalized = data
110
+ self.table(table).chain(tmp_chain).rules.select { |_k, r| r.owned == false }.each do |_k, r|
111
+ normalized = r.description
112
+ end
113
+ self.table(table).delete(tmp_chain, data)
114
+ self.table(table).chain(tmp_chain).deactivate
115
+ self.table(table).chain(tmp_chain).apply_delete
116
+ normalized
117
+ end
118
+ # rubocop:enable Metrics/AbcSize
119
+
120
+ # rubocop:disable Metrics/AbcSize
121
+ def exists(table_name, chain_name, data = nil)
122
+ if data.nil?
123
+ table(table_name).chain_exists(chain_name) && table(table_name).chain(chain_name).active
124
+ else
125
+ data = normalize data, table_name
126
+ table(table_name).chain(chain_name).rules.count { |_k, v| normalize(v.description, table_name) == data && v.active } >= 1
127
+ end
128
+ end
129
+ # rubocop:enable Metrics/AbcSize
130
+
131
+ def insert(table_name, chain_name, data, pos)
132
+ data = normalize data, table_name
133
+ table(table_name).insert(chain_name, data, pos)
134
+ end
135
+
136
+ def append(table_name, chain_name, data)
137
+ data = normalize data, table_name
138
+ table(table_name).append(chain_name, data)
139
+ end
140
+
141
+ def delete(table_name, chain_name, data)
142
+ data = normalize data, table_name
143
+ table(table_name).delete(chain_name, data)
144
+ end
145
+
146
+ def activate
147
+ @active = true
148
+ @tables.each do |_name, table|
149
+ table.activate
150
+ end
151
+ end
152
+
153
+ def deactivate
154
+ @active = false
155
+ tables.each do |_name, table|
156
+ table.deactivate
157
+ end
158
+ end
159
+
160
+ # rubocop:disable Metrics/AbcSize
161
+ def get_active_rules(table = 'nat', chain = 'PREROUTING')
162
+ rrgx = /(?<key>--?[[:alpha:]-]+) (?<value>[[:alnum:]:\.]+)/.freeze
163
+ switch_map = { '-s' => :source, '--source' => :source,
164
+ '-d' => :destination, '--destination' => :destination,
165
+ '--dport' => :dport, '--to-destination' => :to_dest,
166
+ '-p' => :protocol, '--protocol' => :protocol,
167
+ '-j' => :jump, '--jump' => :jump,
168
+ '--match-set' => :match }
169
+
170
+ table(table).chain(chain).rules.filter { |_, r| r.active }.map do |_, rule|
171
+ rule.description.to_enum(:scan, rrgx)
172
+ .map { Regexp.last_match }
173
+ .map { |m| [switch_map[m[:key]], m[:value]] }
174
+ .filter { |m| m[0] }.to_h
175
+ end
176
+ end
177
+
178
+ # used to easily switch rules from one src ip to another
179
+ def switch_sources(old_src, new_src)
180
+ @tables.to_a.each do |_kt, tbl|
181
+ tbl.chains.to_a.each do |_kc, chn|
182
+ next if chn.name.include? 'tablomat'
183
+
184
+ chn.rules.to_a.each do |_key, rule|
185
+ next unless rule.description.include? "-s #{old_src}"
186
+
187
+ new_data = normalize(rule.description, tbl.name).sub "-s #{old_src}", "-s #{new_src}"
188
+ pos = rule.position + 1
189
+ delete(tbl.name, chn.name, rule.description)
190
+ insert(tbl.name, chn.name, new_data, pos)
191
+ end
192
+ end
193
+ end
194
+ end
195
+ # rubocop:enable Metrics/AbcSize
196
+
197
+ def print
198
+ require 'pp'
199
+ pp self
200
+ end
201
+ end
202
+ end
@@ -0,0 +1,170 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tablomat/iptables/rule'
4
+
5
+ module Tablomat
6
+ class IPTables
7
+ # The IPTables class is the interface to the iptables command
8
+ class Chain
9
+ attr_accessor :owned
10
+ attr_reader :table, :name, :active, :rules
11
+
12
+ def initialize(table, name, owned = true)
13
+ @system = table.system
14
+ @table = table
15
+ @name = name
16
+ @policy = 'ACCEPT'
17
+ @rules = {}
18
+ @rules_sorted = []
19
+ @owned = owned
20
+ @active = false
21
+ activate if @table.active
22
+ end
23
+
24
+ def policy(action)
25
+ # set policy as the last rule of the chain
26
+ raise 'Unable to assign policy to non builtin chains, TODO: implement handling' unless builtin?
27
+
28
+ @policy = action
29
+ return unless @active
30
+
31
+ command = "#{@table.system.iptables_bin} -t #{@table.name} -P #{@name} #{@policy}"
32
+ @system.exec command
33
+ end
34
+
35
+ def rule(name, owned = true, &block)
36
+ if name.is_a? Hash
37
+ name = sethandling(name) if name.key?(:set)
38
+ name = name.map { |k, v| "--#{k} #{v}" }.join(' ')
39
+ end
40
+ key = name.to_s.downcase
41
+ (@rules[key] || Rule.new(self, name, owned)).tap do |rule|
42
+ @rules[key] = rule
43
+ block&.call(rule)
44
+ end
45
+ end
46
+
47
+ def sethandling(name)
48
+ trash = {}
49
+ name.each do |k, v|
50
+ trash[k] = v
51
+ trash[:match] = trash.delete :set if trash.key?(:set)
52
+ end
53
+ trash
54
+ end
55
+
56
+ def insert(data, pos)
57
+ rule(data) do |rule|
58
+ rule.method = 'INSERT'
59
+ rule.position = pos
60
+ @rules_sorted.insert(pos - 1, rule)
61
+ update_rules_position
62
+ rule.activate if @active
63
+ end
64
+ end
65
+
66
+ def append(data)
67
+ rule(data) do |rule|
68
+ @rules_sorted << rule
69
+ rule.activate if @active
70
+ end
71
+ end
72
+
73
+ def update_rules_position
74
+ @rules_sorted = @rules_sorted.compact
75
+ @rules_sorted.select(&:active).each_with_index do |rule, index|
76
+ rule.position = index + 1 if (rule.position != 0) && (rule.position != (index + 1))
77
+ end
78
+ end
79
+
80
+ def delete(data)
81
+ rule = if data.is_a? Rule
82
+ data
83
+ else
84
+ self.rule(data)
85
+ end
86
+ rule.deactivate if rule.active
87
+
88
+ @rules_sorted.delete(rule)
89
+ @rules.delete_if { |_k, v| v.description == rule.description }
90
+ end
91
+
92
+ def activate(override = false)
93
+ return unless @owned || override
94
+ return if @active
95
+
96
+ @active = true
97
+ return if override
98
+
99
+ apply_create
100
+ activate_all_rules
101
+ end
102
+
103
+ def deactivate(override = false)
104
+ return unless @owned || override
105
+ return unless @active
106
+
107
+ @active = false
108
+ return if override
109
+
110
+ deactivate_all_rules
111
+ @active = false
112
+ end
113
+
114
+ def apply_create
115
+ unless exists?
116
+ begin
117
+ command = "#{@system.iptables_bin} -t #{@table.name} -N #{@name}"
118
+ @system.exec command
119
+ rescue StandardError
120
+ puts "Error: #{$ERROR_INFO}"
121
+ end
122
+ end
123
+ # apply policy if builtin chain
124
+ return unless builtin?
125
+
126
+ command = "#{@system.iptables_bin} -t #{@table.name} -P #{@name} #{@policy}"
127
+ @system.exec command
128
+ end
129
+
130
+ def apply_delete
131
+ return unless exists? && !builtin?
132
+
133
+ begin
134
+ command = "#{@system.iptables_bin} -t #{@table.name} -F #{@name}"
135
+ @system.exec command
136
+ command = "#{@system.iptables_bin} -t #{@table.name} -X #{@name}"
137
+ @system.exec command
138
+ rescue StandardError
139
+ puts "Error removing chain #{command}, message: #{$ERROR_INFO}"
140
+ end
141
+ end
142
+
143
+ def exists?
144
+ command = "#{@system.iptables_bin} -t #{@table.name} -nL #{@name}"
145
+ @system.exec command
146
+ true
147
+ rescue StandardError
148
+ false
149
+ end
150
+
151
+ def builtin?
152
+ @table.system.builtin_chains.key?(@table.name.to_sym) && @table.system.builtin_chains[@table.name.to_sym].include?(@name)
153
+ end
154
+
155
+ private
156
+
157
+ def activate_all_rules
158
+ @rules_sorted.each do |rule|
159
+ rule.activate if !rule.nil? && !rule.active
160
+ end
161
+ end
162
+
163
+ def deactivate_all_rules
164
+ @rules_sorted.each do |rule|
165
+ rule.deactivate if !rule.nil? && rule.active
166
+ end
167
+ end
168
+ end
169
+ end
170
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tablomat
4
+ class IPTables
5
+ # IPTables are made of Rules
6
+ class Rule
7
+ attr_accessor :owned, :active, :method, :position
8
+ attr_reader :chain, :description
9
+
10
+ def initialize(chain, description, owned = true)
11
+ @system = chain.table.system
12
+ @chain = chain
13
+ @description = description
14
+ @items = {}
15
+ @owned = owned
16
+ @active = false
17
+ @method = 'APPEND'
18
+ @position = 0
19
+ activate if @chain.active
20
+ end
21
+
22
+ def activate(override = false)
23
+ return unless @owned || override
24
+ return if @active
25
+
26
+ @active = true
27
+ return if override
28
+
29
+ @chain.activate unless @chain.active
30
+ apply_create
31
+ end
32
+
33
+ def deactivate(override = false)
34
+ return unless @owned || override
35
+ return unless @active
36
+
37
+ self.active = false
38
+ return if override
39
+
40
+ apply_delete
41
+ end
42
+
43
+ def apply_create
44
+ return unless @owned
45
+
46
+ method = if @method == 'APPEND'
47
+ "-A #{@chain.name}"
48
+ else
49
+ "-I #{@chain.name} #{@position}"
50
+ end
51
+ command = "#{@system.iptables_bin} -t #{@chain.table.name} #{method} #{@description}"
52
+ @system.exec command
53
+ end
54
+
55
+ def apply_delete
56
+ return unless @owned
57
+
58
+ command = "#{@system.iptables_bin} -t #{@chain.table.name} -D #{@chain.name} #{@description}"
59
+ @system.exec command
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'tablomat/iptables/chain'
4
+
5
+ module Tablomat
6
+ class IPTables
7
+ # Puts the Table in IPTables
8
+ class Table
9
+ attr_accessor :owned
10
+ attr_reader :name, :system, :active, :chains
11
+
12
+ def initialize(system, name, owned = true)
13
+ @system = system
14
+ @name = name
15
+ @chains = {}
16
+ # whether this table is owned/created by us or external
17
+ @owned = owned
18
+ @active = false
19
+ activate if @system.active
20
+ end
21
+
22
+ def get_key(name)
23
+ name.to_s.downcase[0, 28]
24
+ end
25
+
26
+ def chain(name, owned = true, &block)
27
+ key = get_key name
28
+ (@chains[key] || Chain.new(self, name, owned)).tap do |chain|
29
+ @chains[key] = chain
30
+ block&.call(chain)
31
+ end
32
+ end
33
+
34
+ def chain_exists(name)
35
+ key = get_key name
36
+ return true if @chains.key? key
37
+
38
+ false
39
+ end
40
+
41
+ def insert(chain_name, data, pos)
42
+ chain chain_name do |chain|
43
+ chain.insert(data, pos)
44
+ end
45
+ end
46
+
47
+ def append(chain_name, data)
48
+ chain(chain_name).append(data)
49
+ end
50
+
51
+ def delete(chain_name, data)
52
+ chain chain_name do |chain|
53
+ chain.delete(data)
54
+ end
55
+ end
56
+
57
+ def activate(override = false)
58
+ return unless @owned || override
59
+
60
+ @active = true
61
+ return if override
62
+
63
+ @chains.each do |_name, chain|
64
+ chain.activate
65
+ end
66
+ end
67
+
68
+ def deactivate(override = false)
69
+ return unless @owned || override
70
+
71
+ @active = false
72
+ return if override
73
+
74
+ @chains.each do |_name, chain|
75
+ chain.deactivate
76
+ end
77
+ end
78
+
79
+ def print
80
+ require 'pp'
81
+ pp self
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Tablomat
4
+ VERSION = '1.2.1'
5
+ end
@@ -0,0 +1,144 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'etc'
5
+
6
+ describe Tablomat::IPSet do
7
+ describe 'Initialize module' do
8
+ @ipset = Tablomat::IPSet.new
9
+ end
10
+
11
+ before(:all) do
12
+ @command = 'ipset'
13
+ @command = "sudo #{@command}" if Etc.getlogin != 'root'
14
+ @ipset = Tablomat::IPSet.new
15
+ end
16
+
17
+ before(:each) do
18
+ @ipset.destroy_all(true)
19
+ end
20
+
21
+ after(:all) do
22
+ # destroy all sets
23
+ @ipset.destroy_all(true)
24
+ end
25
+
26
+ it 'creates new sets of type hash:ip' do
27
+ @ipset.create(set_name: 'test1', type: 'hash:ip')
28
+ expect(@ipset.set('test1').exists?).to be_truthy
29
+ end
30
+
31
+ it 'can destroy existing sets' do
32
+ @ipset.create(set_name: 'test2', type: 'hash:ip')
33
+ expect(@ipset.set('test2').exists?).to be_truthy
34
+ @ipset.destroy(set_name: 'test2')
35
+ expect(@ipset.set('test2').exists?).to be_falsey
36
+ end
37
+
38
+ it 'set() returns instance of Set' do
39
+ expect(@ipset.set('test2').exists?).to be_falsey
40
+ set_set = @ipset.set('test2')
41
+ set_set.create(type: 'hash:ip')
42
+ expect(@ipset.set('test2').exists?).to be_truthy
43
+ end
44
+
45
+ it 'checks if set exists' do
46
+ # check precondition
47
+ expect(@ipset.set('test3').exists?).to be_falsy
48
+
49
+ # check existing
50
+ @ipset.create(set_name: 'test3', type: 'hash:ip')
51
+ expect(@ipset.set('test3').exists?).to be_truthy
52
+
53
+ # check missing
54
+ @ipset.destroy(set_name: 'test3')
55
+ expect(@ipset.set('test3').exists?).to be_falsy
56
+ end
57
+
58
+ it 'can not create set if set with same name already exists' do
59
+ @ipset.create(set_name: 'test4', type: 'hash:ip')
60
+ expect(@ipset.set('test4').exists?).to be_truthy
61
+ expect { @ipset.create(set_name: 'test4', type: 'hash:port') }.to raise_error(Tablomat::IPSetError)
62
+ end
63
+
64
+ it 'can not delete set if the set does not exist' do
65
+ expect(@ipset.set('test6').exists?).to be_falsey
66
+ expect { @ipset.destroy(set_name: 'test6') }.to raise_error(Tablomat::IPSetError)
67
+ end
68
+
69
+ it 'appends entrys to set in ipset' do
70
+ @ipset.create(set_name: 'test6', type: 'hash:ip')
71
+ expect(@ipset.set('test6').entry('192.0.2.42').exists?).to be_falsy
72
+ @ipset.add(set_name: 'test6', entry_data: '192.0.2.42')
73
+ expect(@ipset.set('test6').entry('192.0.2.42').exists?).to be_truthy
74
+ end
75
+
76
+ it 'appends entrys to set in set' do
77
+ @ipset.create(set_name: 'test6', type: 'hash:ip')
78
+ expect(@ipset.set('test6').entry('192.0.2.52').exists?).to be_falsy
79
+ @ipset.set('test6').add(entry_data: '192.0.2.52')
80
+ expect(@ipset.set('test6').entry('192.0.2.52').exists?).to be_truthy
81
+ end
82
+
83
+ it 'can delete entrys in sets in ipset' do
84
+ @ipset.create(set_name: 'test7', type: 'hash:ip')
85
+ @ipset.add(set_name: 'test7', entry_data: '192.0.2.42')
86
+ expect(@ipset.set('test7').entry('192.0.2.42').exists?).to be_truthy
87
+ @ipset.del(set_name: 'test7', entry_data: '192.0.2.42')
88
+ expect(@ipset.set('test7').entry('192.0.2.42').exists?).to be_falsy
89
+ end
90
+
91
+ it 'can delete entrys in sets in set' do
92
+ @ipset.create(set_name: 'test7', type: 'hash:ip')
93
+ @ipset.set('test7').add(entry_data: '192.0.2.42')
94
+ expect(@ipset.set('test7').entry('192.0.2.42').exists?).to be_truthy
95
+ @ipset.set('test7').del(entry_data: '192.0.2.42')
96
+ expect(@ipset.set('test7').entry('192.0.2.42').exists?).to be_falsy
97
+ end
98
+
99
+ it 'creates set with type bitmap:port' do
100
+ @ipset.create(set_name: 'test8', type: 'bitmap:port', rangefrom: 500_00, rangeto: 600_00)
101
+ expect(@ipset.set('test8').exists?).to be_truthy
102
+ expect(@ipset.set('test8').type == 'bitmap:port').to be_truthy
103
+ end
104
+
105
+ it 'creates set with type hash:ip,port' do
106
+ @ipset.create(set_name: 'test9', type: 'hash:ip,port')
107
+ expect(@ipset.set('test9').exists?).to be_truthy
108
+ expect(@ipset.set('test9').type == 'hash:ip,port').to be_truthy
109
+ end
110
+
111
+ it 'can destroy created in one instance' do
112
+ # creating sets in an instance
113
+ @ipset.create(set_name: 'test10', type: 'bitmap:port', rangefrom: 500_00, rangeto: 600_00)
114
+ expect(@ipset.set('test10').exists?).to be_truthy
115
+ @ipset.create(set_name: 'test11', type: 'hash:mac')
116
+ expect(@ipset.set('test11').exists?).to be_truthy
117
+ # creating sets in another instance
118
+ ipsetnext = Tablomat::IPSet.new
119
+ ipsetnext.create(set_name: 'test12', type: 'bitmap:port', rangefrom: 500_00, rangeto: 600_00)
120
+ expect(@ipset.set('test12').exists?).to be_truthy
121
+ ipsetnext.create(set_name: 'test13', type: 'hash:ip,port')
122
+ expect(@ipset.set('test13').exists?).to be_truthy
123
+ ipsetnext.create(set_name: 'test14', type: 'hash:net')
124
+ expect(@ipset.set('test14').exists?).to be_truthy
125
+ # destroy sets created with ipsetnext
126
+ ipsetnext.destroy_all
127
+ expect(@ipset.set('test10').exists?).to be_truthy
128
+ expect(@ipset.set('test11').exists?).to be_truthy
129
+ expect(@ipset.set('test12').exists?).to be_falsey
130
+ expect(@ipset.set('test13').exists?).to be_falsey
131
+ expect(@ipset.set('test14').exists?).to be_falsey
132
+ end
133
+
134
+ it 'can destroy all sets' do
135
+ # creating sets in another instance
136
+ ipsetnext = Tablomat::IPSet.new
137
+ ipsetnext.create(set_name: 'test14', type: 'hash:net')
138
+ expect(@ipset.set('test14').exists?).to be_truthy
139
+ ipsetnext.destroy_all(true)
140
+ expect(@ipset.set('test10').exists?).to be_falsey
141
+ expect(@ipset.set('test11').exists?).to be_falsey
142
+ expect(@ipset.set('test14').exists?).to be_falsey
143
+ end
144
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'etc'
5
+
6
+ describe Tablomat::IPTables, Tablomat::IPSet do
7
+ describe 'Initialize module' do
8
+ @ipset = Tablomat::IPSet.new
9
+ @iptables = Tablomat::IPTables.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::IPTables.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')
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')
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')
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')
69
+ @iptables.append('mangle', 'custom', source: '10.0.0.1', 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: '10.0.0.1', protocol: :tcp, sport: 123)
73
+ @ipset.destroy(set_name: 'testset5')
74
+ end
75
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'spec_helper'
4
+ require 'etc'
5
+
6
+ describe Tablomat::IPTables do
7
+ describe 'Initialize module' do
8
+ @iptables = Tablomat::IPTables.new
9
+ end
10
+
11
+ before(:all) do
12
+ @command = 'iptables'
13
+ @command = "sudo #{@command}" if Etc.getlogin != 'root'
14
+
15
+ @iptables = Tablomat::IPTables.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: '1.2.3.4')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
44
+ expect(@iptables.normalize('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')).to eq('-s 1.2.3.4/32 -p tcp -m tcp --sport 123')
45
+ expect(@iptables.normalize('-s 1.2.3.4/32 -m tcp --sport 123 -p tcp')).to eq('-s 1.2.3.4/32 -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: '127.0.0.2',
72
+ destination: '127.0.0.3',
73
+ protocol: :tcp,
74
+ dport: 8080,
75
+ jump: 'DNAT', to: '127.0.0.4:9090')
76
+ target_hash = { source: '127.0.0.2', destination: '127.0.0.3', dport: '8080', jump: 'DNAT', protocol: 'tcp', to_dest: '127.0.0.4:9090' }
77
+ rules = @iptables.get_active_rules
78
+ expect(rules.length).to eq(2)
79
+ expect(rules.pop).to eq(target_hash)
80
+ @iptables.delete('nat', 'PREROUTING', source: '127.0.0.2',
81
+ destination: '127.0.0.3',
82
+ protocol: :tcp,
83
+ dport: 8080,
84
+ jump: 'DNAT', to: '127.0.0.4: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: '1.2.3.5')
140
+ @iptables.switch_sources('1.2.3.5', '5.3.2.1')
141
+ expect(@iptables.exists('filter', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123)).to be_truthy
142
+ expect(@iptables.exists('filter', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123)).to be_falsey
143
+ @iptables.append('nat', 'rspec', protocol: :tcp, sport: 123, source: '1.2.3.5', jump: :DNAT, 'to-destination': '127.0.0.1:123')
144
+ @iptables.switch_sources('1.2.3.5', '5.3.2.1')
145
+ expect(@iptables.exists('nat', 'rspec', source: '5.3.2.1', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_truthy
146
+ expect(@iptables.exists('nat', 'rspec', source: '1.2.3.5', protocol: :tcp, sport: 123, jump: :DNAT, 'to-destination': '127.0.0.1:123')).to be_falsey
147
+ end
148
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'simplecov'
4
+ SimpleCov.start do
5
+ enable_coverage :branch
6
+ add_filter '/spec/'
7
+ end
8
+
9
+ require 'rubygems'
10
+ require 'bundler/setup'
11
+ require 'tablomat'
12
+
13
+ RSpec.configure do |config|
14
+ # RSpec::Core::Configuration#treat_symbols_as_metadata_keys_with_true_values= is deprecated, it is now set to true as default and setting it to false has no effect.
15
+ # config.treat_symbols_as_metadata_keys_with_true_values = true
16
+ config.raise_errors_for_deprecations!
17
+ config.run_all_when_everything_filtered = true
18
+ config.filter_run :focus
19
+
20
+ config.order = 'random'
21
+ end
metadata ADDED
@@ -0,0 +1,133 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tablomat
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.2.1
5
+ platform: ruby
6
+ authors:
7
+ - Matthias Wübbeling
8
+ - Arnold Sykosch
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2021-03-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 13.0.1
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 13.0.1
28
+ - !ruby/object:Gem::Dependency
29
+ name: rspec
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: 3.9.0
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: 3.9.0
42
+ - !ruby/object:Gem::Dependency
43
+ name: rubocop
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - "~>"
47
+ - !ruby/object:Gem::Version
48
+ version: 0.88.0
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - "~>"
54
+ - !ruby/object:Gem::Version
55
+ version: 0.88.0
56
+ - !ruby/object:Gem::Dependency
57
+ name: rubocop-performance
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 1.7.1
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 1.7.1
70
+ - !ruby/object:Gem::Dependency
71
+ name: simplecov
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: 0.18.5
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: 0.18.5
84
+ description: A simple wrapper for iptables
85
+ email:
86
+ - matthias.wuebbeling@cs.uni-bonn.de
87
+ - sykosch@cs.uni-bonn.de
88
+ executables: []
89
+ extensions: []
90
+ extra_rdoc_files: []
91
+ files:
92
+ - lib/tablomat.rb
93
+ - lib/tablomat/exec.rb
94
+ - lib/tablomat/ipset.rb
95
+ - lib/tablomat/ipset/entry.rb
96
+ - lib/tablomat/ipset/set.rb
97
+ - lib/tablomat/iptables.rb
98
+ - lib/tablomat/iptables/chain.rb
99
+ - lib/tablomat/iptables/rule.rb
100
+ - lib/tablomat/iptables/table.rb
101
+ - lib/tablomat/version.rb
102
+ - spec/ipset_spec.rb
103
+ - spec/ipsetiptables_spec.rb
104
+ - spec/iptables_spec.rb
105
+ - spec/spec_helper.rb
106
+ homepage: https://gitlab.com/itsape/tablomat
107
+ licenses:
108
+ - MIT
109
+ metadata: {}
110
+ post_install_message:
111
+ rdoc_options: []
112
+ require_paths:
113
+ - lib
114
+ required_ruby_version: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ required_rubygems_version: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - ">="
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ requirements: []
125
+ rubygems_version: 3.0.3
126
+ signing_key:
127
+ specification_version: 4
128
+ summary: This wrapper provides an interface to iptables.
129
+ test_files:
130
+ - spec/ipset_spec.rb
131
+ - spec/ipsetiptables_spec.rb
132
+ - spec/iptables_spec.rb
133
+ - spec/spec_helper.rb