shutter 0.0.7 → 0.1.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.
@@ -0,0 +1,31 @@
1
+ module Shutter
2
+ class Files
3
+ include Shutter::Content
4
+
5
+ class << self
6
+ include Shutter::Content
7
+ def create(dir, overwrite=false, except=[])
8
+ CONFIG_FILES.each do |name|
9
+ file = "#{dir}/#{name}"
10
+ if !File.exists?(file) || overwrite || except.include?(name)
11
+ File.open(file, 'w') do |f|
12
+ f.write(const_get(name.upcase.gsub(/\./, "_")))
13
+ end
14
+ end
15
+ end
16
+ end
17
+
18
+ def create_config_dir(config_path)
19
+ # Check to see if the path to the config files exist
20
+ unless File.directory?(config_path)
21
+ begin
22
+ Dir.mkdir(config_path)
23
+ rescue Errno::ENOENT
24
+ raise "Could not create the configuration directory. Check to see if the parent directory exists."
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ end
31
+ end
@@ -1,13 +1,218 @@
1
- require 'shutter/iptables/base'
2
- require 'shutter/iptables/eyepee'
3
- require 'shutter/iptables/iface'
4
- require 'shutter/iptables/jail'
5
- require 'shutter/iptables/port'
6
- require 'shutter/iptables/forward'
7
- require 'shutter/os'
8
-
9
1
  module Shutter
10
- module IPTables
11
- IPTABLES_RESTORE="/sbin/iptables-restore"
2
+ module Firewall
3
+ class IPTables
4
+ RULES_DMZ_BLOCK = "# [RULES:DMZ]"
5
+ RULES_FORWARD_BLOCK = "# [RULES:FORWARD]"
6
+ RULES_POSTROUTING_BLOCK = "# [RULES:POSTROUTING]"
7
+ RULES_BASTARDS_BLOCK = "# [RULES:BASTARDS]"
8
+ RULES_PUBLIC_BLOCK = "# [RULES:PUBLIC]"
9
+ RULES_ALLOWIP_BLOCK = "# [RULES:ALLOWIP]"
10
+ RULES_PRIVATE_BLOCK = "# [RULES:PRIVATE]"
11
+ RULES_FAIL2BAN_BLOCK = "# [RULES:FAIL2BAN]"
12
+ RULES_JAIL_BLOCK = "# [RULES:JAIL]"
13
+ CHAIN_FAIL2BAN_BLOCK = "# [CHAIN:FAIL2BAN]"
14
+
15
+ def initialize(path)
16
+ @path = path
17
+ @base = read("base.ipt",false).join("\n")
18
+ @iface_forward = read("iface.forward")
19
+ @ports_private = read("ports.private")
20
+ @ports_public = read("ports.public")
21
+ @ip_allow = read("ip.allow")
22
+ @ip_deny = read("ip.deny")
23
+ @dmz_device = read("iface.dmz")
24
+ @os = Shutter::OS.new
25
+ end
26
+
27
+ def base_sub(block,content)
28
+ @base = @base.gsub(/#{Regexp.quote(block)}/, content)
29
+ end
30
+
31
+ def generate
32
+ base_sub(RULES_DMZ_BLOCK, dmz_device_block)
33
+ base_sub(RULES_FORWARD_BLOCK, forward_block)
34
+ base_sub(RULES_POSTROUTING_BLOCK, postrouting_block)
35
+ base_sub(RULES_BASTARDS_BLOCK, deny_ip_block)
36
+ base_sub(RULES_PUBLIC_BLOCK, allow_public_port_block)
37
+ base_sub(RULES_ALLOWIP_BLOCK, allow_ip_block)
38
+ base_sub(RULES_PRIVATE_BLOCK, allow_private_port_block)
39
+ base_sub(RULES_FAIL2BAN_BLOCK, fail2ban_rules_block)
40
+ base_sub(RULES_JAIL_BLOCK, jail_rules_block)
41
+ base_sub(CHAIN_FAIL2BAN_BLOCK, fail2ban_chains_block)
42
+ clean
43
+ end
44
+
45
+ def to_s
46
+ @base
47
+ end
48
+
49
+ def clean
50
+ @base = @base.gsub(/^#.*$/, "")
51
+ @base = @base.gsub(/^$\n/, "")
52
+ end
53
+
54
+ def read(file, filter=true)
55
+ #puts "Reading: #{@path}/#{file}"
56
+ lines = File.read("#{@path}/#{file}").split("\n")
57
+ # Doesn't work with 1.8.x
58
+ # lines.keep_if{ |line| line =~ /^[a-z0-9].+$/ } if filter
59
+ # so since we are iterating through this, well handle the stripping as well
60
+ # lines.map { |line| line.strip }
61
+ newlines = []
62
+ lines.each do |line|
63
+ if filter
64
+ newlines << line.strip if line =~ /^[a-z0-9].+$/
65
+ else
66
+ newlines << line.strip
67
+ end
68
+ end
69
+ newlines
70
+ end
71
+
72
+ def save
73
+ puts self.generate
74
+ end
75
+
76
+ def restore
77
+ IO.popen("#{iptables_restore}", "r+") do |iptr|
78
+ iptr.puts self.generate ; iptr.close_write
79
+ end
80
+ end
81
+
82
+ def persist(pfile)
83
+ File.open(pfile, "w") do |f|
84
+ f.write(@base)
85
+ end
86
+ end
87
+
88
+ ###
89
+ ### IPTables Commands
90
+ ###
91
+ def iptables_save
92
+ @iptable_save ||= `"#{@os.iptables_save}"`
93
+ end
94
+
95
+ def iptables_restore
96
+ "#{@os.iptables_restore}"
97
+ end
98
+
99
+ ###
100
+ ### Block Generation
101
+ ###
102
+ def forward_block
103
+ content = ""
104
+ @iface_forward.each do |line|
105
+ src, dst = line.split(' ')
106
+ content += self.forward_content(src,dst)
107
+ end
108
+ content
109
+ end
110
+
111
+ def postrouting_block
112
+ masq_ifaces = []
113
+ content = ""
114
+ @iface_forward.each do |line|
115
+ src, dst = line.split(' ')
116
+ content += self.postrouting_content(dst) unless masq_ifaces.include?(dst)
117
+ masq_ifaces << dst
118
+ end
119
+ content
120
+ end
121
+
122
+ def allow_private_port_block
123
+ content = ""
124
+ @ports_private.each do |line|
125
+ port,proto = line.split
126
+ content += self.allow_private_port_content(port, proto)
127
+ end
128
+ content
129
+ end
130
+
131
+ def allow_public_port_block
132
+ content = ""
133
+ @ports_public.each do |line|
134
+ port,proto = line.split
135
+ raise "Invalid port in port.allow" unless port =~ /^[0-9].*$/
136
+ raise "Invalid protocol in port.allow" unless proto =~ /^(tcp|udp)$/
137
+ content += self.allow_public_port_content(port, proto)
138
+ end
139
+ content
140
+ end
141
+
142
+ def allow_ip_block
143
+ content = ""
144
+ @ip_allow.each do |line|
145
+ raise "Invalid IP address in ip.allow" unless line =~ /^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}(\/[0-9]{0,2})*$/
146
+ content += self.allow_ip_content(line)
147
+ end
148
+ content
149
+ end
150
+
151
+ def deny_ip_block
152
+ content = ""
153
+ @ip_deny.each do |line|
154
+ raise "Invalid IP address in ip.deny" unless line =~ /^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}(\/[0-9]{0,2})*$/
155
+ content += self.deny_ip_content(line)
156
+ end
157
+ content
158
+ end
159
+
160
+ def dmz_device_block
161
+ content = ""
162
+ @dmz_device.each do |line|
163
+ raise "Invalid device in iface.dmz" unless line =~ /^[a-z][a-z0-9].*$/
164
+ content += self.dmz_device_content(line)
165
+ end
166
+ content
167
+ end
168
+
169
+ def fail2ban_chains_block
170
+ iptables_save.scan(/^:fail2ban.*$/).join("\n")
171
+ end
172
+
173
+ def fail2ban_rules_block
174
+ iptables_save.scan(/^-A fail2ban.*$/).join("\n")
175
+ end
176
+
177
+ def jail_rules_block
178
+ lines = iptables_save.scan(/^-A Jail.*$/)
179
+ lines << "-A Jail -j RETURN\n" unless lines.last =~ /-A Jail -j RETURN/
180
+ lines.join("\n")
181
+ end
182
+
183
+ ###
184
+ ### Block Content
185
+ ###
186
+ def forward_content(src,dst)
187
+ rule = "-A FORWARD -i #{src} -o #{dst} -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT\n"
188
+ rule += "-A FORWARD -i #{dst} -o #{src} -m state --state RELATED,ESTABLISHED -j ACCEPT\n"
189
+ rule
190
+ end
191
+
192
+ def postrouting_content(iface)
193
+ "-A POSTROUTING -o #{iface} -j MASQUERADE\n"
194
+ end
195
+
196
+ def allow_private_port_content(port, proto)
197
+ "-A Private -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j RETURN\n"
198
+ end
199
+
200
+ def allow_public_port_content(port, proto)
201
+ "-A Public -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j ACCEPT\n"
202
+ end
203
+
204
+ def allow_ip_content(ip)
205
+ "-A AllowIP -m state --state NEW -s #{ip} -j Allowed\n"
206
+ end
207
+
208
+ def deny_ip_content(ip)
209
+ "-A Bastards -s #{ip} -j DropBastards\n"
210
+ end
211
+
212
+ def dmz_device_content(iface)
213
+ "-A Dmz -i #{iface} -j ACCEPT\n"
214
+ end
215
+
216
+ end
12
217
  end
13
218
  end
data/lib/shutter/os.rb CHANGED
@@ -6,6 +6,12 @@ module Shutter
6
6
  end
7
7
  end
8
8
 
9
+ def validate!
10
+ if unknown?
11
+ raise "ERROR: Unsupported operating system"
12
+ end
13
+ end
14
+
9
15
  def family
10
16
  @family ||= ENV['OS'] ? ENV['OS'] : RUBY_PLATFORM.split('-').last
11
17
  end
@@ -15,7 +21,28 @@ module Shutter
15
21
  end
16
22
 
17
23
  def linux?
18
- return family == "linux"
24
+ family == "linux"
25
+ end
26
+
27
+ def iptables_save
28
+ "/sbin/iptables-save"
29
+ end
30
+
31
+ def iptables_restore
32
+ "/sbin/iptables-restore"
33
+ end
34
+
35
+ def persist_file
36
+ case version
37
+ when /Red Hat/
38
+ "/etc/sysconfig/iptables"
39
+ when /Debian/
40
+ "/etc/iptables/rules"
41
+ when /Ubuntu/
42
+ "/etc/iptables/rules"
43
+ else
44
+ "/tmp/iptables.rules"
45
+ end
19
46
  end
20
47
 
21
48
  def dist
@@ -35,6 +62,18 @@ module Shutter
35
62
  dist == "RedHat"
36
63
  end
37
64
 
65
+ def ubuntu?
66
+ dist == "Ubuntu"
67
+ end
68
+
69
+ def debian?
70
+ dist == "Debian"
71
+ end
72
+
73
+ def unknown?
74
+ dist == "Unknown"
75
+ end
76
+
38
77
  alias :centos? :redhat?
39
78
  alias :fedora? :redhat?
40
79
  end
@@ -1,3 +1,3 @@
1
1
  module Shutter
2
- VERSION = "0.0.7"
2
+ VERSION = "0.1.0"
3
3
  end
data/shutter.gemspec CHANGED
@@ -6,9 +6,9 @@ Gem::Specification.new do |gem|
6
6
  gem.email = ["nosignsoflifehere@gmail.com"]
7
7
  gem.description = %q{Shutter is a tool that gives system administrators the ability
8
8
  to manage iptables firewall settings through simple lists instead
9
- of complex iptables rules. Please note: This application currently
10
- only works with Red Hat based distributions, as the need arrises
11
- more distributions will be added.
9
+ of complex iptables rules. Please note: This application is currently
10
+ only tested with Red Hat based distributions. Ubuntu and Debian should
11
+ work but are not supported..
12
12
  }
13
13
  gem.summary = %q{Shutter helps manage iptables firewalls}
14
14
  gem.homepage = ""
@@ -21,4 +21,5 @@ Gem::Specification.new do |gem|
21
21
  gem.version = Shutter::VERSION
22
22
  gem.add_development_dependency('rspec')
23
23
  gem.add_development_dependency('mocha')
24
+ gem.add_development_dependency('simplecov')
24
25
  end
@@ -1,16 +1,82 @@
1
1
  require File.dirname(__FILE__) + '/spec_helper'
2
- require 'fileutils'
3
2
 
4
3
  describe "Shutter::CommandLine" do
5
- it "should create the configuration directory if it does not exist" do
6
- cmd = Shutter::CommandLine.new('./tmp/configs')
7
- cmd.init
8
- File.directory?('./tmp/configs').should == true
9
- FileUtils.rm_rf('./tmp/configs')
4
+ before(:each) do
5
+ @cmd = Shutter::CommandLine.new("./tmp")
10
6
  end
11
7
 
12
- it "should not recursively create the configuration directory if the parent does not exist" do
13
- cmd = Shutter::CommandLine.new('./tmp/configs/this')
14
- expect { cmd.init }.to raise_error
8
+ it "should not raise exception when firewall is called" do
9
+ expect { @cmd.firewall }.to_not raise_error
15
10
  end
11
+
12
+ it "should set default value of persist to false" do
13
+ @cmd.persist.should == false
14
+ end
15
+
16
+ it "should set default value of debug to false" do
17
+ @cmd.debug.should == false
18
+ end
19
+
20
+ it "should have set config_path to ./tmp" do
21
+ @cmd.config_path.should == "./tmp"
22
+ end
23
+
24
+ it "should set the command to :save" do
25
+ @cmd.execute(["--save"],true)
26
+ @cmd.command.should == :save
27
+ @cmd.execute(["-s"],true)
28
+ @cmd.command.should == :save
29
+ end
30
+
31
+ it "should set the command to :restore" do
32
+ @cmd.execute(["--restore"],true)
33
+ @cmd.command.should == :restore
34
+ @cmd.execute(["--restore", "--persist"],true)
35
+ @cmd.command.should == :restore
36
+ @cmd.persist.should == true
37
+ end
38
+
39
+ it "should set the command to :init" do
40
+ @cmd.execute(["--init"],true)
41
+ @cmd.command.should == :init
42
+ end
43
+
44
+ it "should set the command to :reinit" do
45
+ @cmd.execute(["--reinit"],true)
46
+ @cmd.command.should == :reinit
47
+ end
48
+
49
+ it "should set the command to :upgrade" do
50
+ @cmd.execute(["--upgrade"],true)
51
+ @cmd.command.should == :upgrade
52
+ end
53
+
54
+ it "should set the config path and persist" do
55
+ Shutter::OS.stubs(:version).returns("Unknown")
56
+ @cmd.execute(["--dir", "/tmp", "--restore", "--persist"],true)
57
+ @cmd.command.should == :restore
58
+ @cmd.persist.should == true
59
+ @cmd.persist_file.should == "/tmp/iptables.rules"
60
+ @cmd.config_path.should == "/tmp"
61
+ @cmd.execute(["-d", "/tmp", "--restore", "--persist"],true)
62
+ @cmd.command.should == :restore
63
+ @cmd.persist.should == true
64
+ @cmd.persist_file.should == "/tmp/iptables.rules"
65
+ @cmd.config_path.should == "/tmp"
66
+ end
67
+
68
+ it "should set the config path and persist with file" do
69
+ Shutter::OS.stubs(:version).returns("Unknown")
70
+ @cmd.execute(["--dir", "/tmp", "--restore", "--persist", "/tmp/persistance.file"],true)
71
+ @cmd.command.should == :restore
72
+ @cmd.persist.should == true
73
+ @cmd.persist_file.should == "/tmp/persistance.file"
74
+ @cmd.config_path.should == "/tmp"
75
+ @cmd.execute(["-d", "/tmp", "--restore", "--persist", "/tmp/persistance.file"],true)
76
+ @cmd.command.should == :restore
77
+ @cmd.persist.should == true
78
+ @cmd.persist_file.should == "/tmp/persistance.file"
79
+ @cmd.config_path.should == "/tmp"
80
+ end
81
+
16
82
  end
data/spec/content_spec.rb CHANGED
@@ -2,8 +2,8 @@ require File.dirname(__FILE__) + '/spec_helper'
2
2
 
3
3
  describe "Shutter" do
4
4
  it "should have templates for all files" do
5
- Shutter::CONFIG_FILES.each do |name|
6
- Shutter.constants.include?(:"#{name.upcase.gsub(/\./, "_")}").should == true
5
+ Shutter::Content::CONFIG_FILES.each do |name|
6
+ Shutter::Content.constants.include?(:"#{name.upcase.gsub(/\./, "_")}").should == true
7
7
  end
8
8
  end
9
9
  end