shutter 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/shutter.rb +8 -1
- data/lib/shutter/command_line.rb +96 -62
- data/lib/shutter/content.rb +3 -3
- data/lib/shutter/iptables.rb +4 -3
- data/lib/shutter/iptables/base.rb +42 -38
- data/lib/shutter/iptables/eyepee.rb +28 -28
- data/lib/shutter/iptables/iface.rb +25 -25
- data/lib/shutter/iptables/jail.rb +21 -21
- data/lib/shutter/iptables/port.rb +29 -29
- data/lib/shutter/os.rb +41 -0
- data/lib/shutter/version.rb +1 -1
- metadata +2 -1
data/lib/shutter.rb
CHANGED
data/lib/shutter/command_line.rb
CHANGED
@@ -1,71 +1,105 @@
|
|
1
1
|
require 'optparse'
|
2
2
|
require 'shutter/iptables'
|
3
|
+
require 'shutter/os'
|
3
4
|
|
4
5
|
module Shutter
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
ports.private
|
15
|
-
ports.public
|
16
|
-
]
|
17
|
-
files.each do |name|
|
18
|
-
file = "#{@config_path}/#{name}"
|
19
|
-
unless File.exists?(file)
|
20
|
-
# puts "Creating: #{file}"
|
21
|
-
File.open(file, 'w') do |f|
|
22
|
-
f.write(Shutter.const_get(name.upcase.gsub(/\./, "_")))
|
23
|
-
end
|
24
|
-
end
|
25
|
-
end
|
26
|
-
end
|
6
|
+
class CommandLine
|
7
|
+
def initialize( path = "/etc/shutter.d")
|
8
|
+
# Currently only available to RedHat variants
|
9
|
+
@os = Shutter::OS.new
|
10
|
+
unless @os.redhat?
|
11
|
+
puts "Shutter is currently only compatible with RedHat and its variants."
|
12
|
+
puts "Help make it compatible with others (github.com/rlyon/shutter)"
|
13
|
+
exit
|
14
|
+
end
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
options[:command] = :save
|
33
|
-
opts.on( '-s', '--save', 'Output the firewall to stdout.') do
|
34
|
-
options[:command] = :save
|
35
|
-
end
|
36
|
-
opts.on( '-r', '--restore', 'Load the firewall through iptables-restore.') do
|
37
|
-
options[:command] = :restore
|
38
|
-
end
|
39
|
-
options[:debug] = false
|
40
|
-
opts.on( '-d', '--debug', 'Be a bit more chatty') do
|
41
|
-
options[:debug] = true
|
42
|
-
end
|
43
|
-
opts.on_tail( '-h', '--help', 'Display this screen' ) do
|
44
|
-
puts opts
|
45
|
-
exit
|
46
|
-
end
|
47
|
-
opts.on_tail( '--version', "Show the version") do
|
48
|
-
puts Shutter::VERSION
|
49
|
-
exit
|
50
|
-
end
|
51
|
-
end
|
52
|
-
optparse.parse!
|
53
|
-
puts "* Using config path: #{@config_path}" if options[:debug]
|
54
|
-
puts "* Running command: #{options[:command].to_s}" if options[:debug]
|
55
|
-
send(options[:command])
|
56
|
-
end
|
16
|
+
@config_path = path
|
17
|
+
@iptables = Shutter::IPTables::Base.new(@config_path)
|
18
|
+
|
19
|
+
end
|
57
20
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
21
|
+
def execute
|
22
|
+
options = {}
|
23
|
+
optparse = OptionParser.new do |opts|
|
24
|
+
opts.banner = "Usage: shutter [options]"
|
25
|
+
options[:command] = :save
|
26
|
+
opts.on( '--init', 'Create the initial configuration files' ) do
|
27
|
+
options[:command] = :init
|
28
|
+
end
|
29
|
+
opts.on( '--reinit', 'Rereate the initial configuration files' ) do
|
30
|
+
options[:command] = :reinit
|
31
|
+
end
|
32
|
+
opts.on( '-s', '--save', 'Output the firewall to stdout. (DEFAULT)') do
|
33
|
+
options[:command] = :save
|
34
|
+
end
|
35
|
+
opts.on( '-r', '--restore', 'Load the firewall through iptables-restore.') do
|
36
|
+
options[:command] = :restore
|
37
|
+
end
|
38
|
+
@persist = false
|
39
|
+
opts.on( '-p', '--persist', 'Make the changes persistant. (with --restore)') do
|
40
|
+
@persist = true
|
41
|
+
end
|
42
|
+
options[:debug] = false
|
43
|
+
opts.on( '-d', '--debug', 'Be a bit more chatty') do
|
44
|
+
options[:debug] = true
|
45
|
+
end
|
46
|
+
opts.on_tail( '-h', '--help', 'Display this screen' ) do
|
47
|
+
puts opts
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
opts.on_tail( '--version', "Show the version") do
|
51
|
+
puts Shutter::VERSION
|
52
|
+
exit
|
53
|
+
end
|
54
|
+
end
|
55
|
+
optparse.parse!
|
56
|
+
puts "* Using config path: #{@config_path}" if @debug
|
57
|
+
puts "* Running command: #{options[:command].to_s}" if @debug
|
58
|
+
send(options[:command])
|
59
|
+
end
|
62
60
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
61
|
+
def init
|
62
|
+
Shutter::CONFIG_FILES.each do |name|
|
63
|
+
file = "#{@config_path}/#{name}"
|
64
|
+
unless File.exists?(file)
|
65
|
+
# puts "Creating: #{file}"
|
66
|
+
File.open(file, 'w') do |f|
|
67
|
+
f.write(Shutter.const_get(name.upcase.gsub(/\./, "_")))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
69
72
|
|
70
|
-
|
73
|
+
def reinit
|
74
|
+
Shutter::CONFIG_FILES.each do |name|
|
75
|
+
file = "#{@config_path}/#{name}"
|
76
|
+
File.open(file, 'w') do |f|
|
77
|
+
f.write(Shutter.const_get(name.upcase.gsub(/\./, "_")))
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def save
|
83
|
+
init
|
84
|
+
@ipt = @iptables.generate
|
85
|
+
puts @ipt
|
86
|
+
end
|
87
|
+
|
88
|
+
def restore
|
89
|
+
init
|
90
|
+
@ipt = @iptables.generate
|
91
|
+
IO.popen("#{Shutter::IPTables::IPTABLES_RESTORE}", "r+") do |iptr|
|
92
|
+
iptr.puts @ipt ; iptr.close_write
|
93
|
+
end
|
94
|
+
persist if @persist
|
95
|
+
end
|
96
|
+
|
97
|
+
def persist
|
98
|
+
pfile = ENV['SHUTTER_PERSIST_FILE'] ? ENV['SHUTTER_PERSIST_FILE'] : @iptables.persist_file(@os)
|
99
|
+
File.open(pfile, "w") do |f|
|
100
|
+
f.write(@ipt)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
end
|
71
105
|
end
|
data/lib/shutter/content.rb
CHANGED
data/lib/shutter/iptables.rb
CHANGED
@@ -3,9 +3,10 @@ require 'shutter/iptables/eyepee'
|
|
3
3
|
require 'shutter/iptables/iface'
|
4
4
|
require 'shutter/iptables/jail'
|
5
5
|
require 'shutter/iptables/port'
|
6
|
+
require 'shutter/os'
|
6
7
|
|
7
8
|
module Shutter
|
8
|
-
|
9
|
-
|
10
|
-
|
9
|
+
module IPTables
|
10
|
+
IPTABLES_RESTORE="/sbin/iptables-restore"
|
11
|
+
end
|
11
12
|
end
|
@@ -1,45 +1,49 @@
|
|
1
1
|
module Shutter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module IPTables
|
3
|
+
class Base
|
4
|
+
def initialize( path )
|
5
|
+
@path = path
|
6
|
+
file = File.open("#{path}/base.ipt", "r")
|
7
|
+
@content = file.read
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def persist_file(os)
|
11
|
+
"/etc/sysconfig/iptables"
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
end
|
14
|
+
def to_s
|
15
|
+
@content
|
16
|
+
end
|
18
17
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
@content = @content.gsub(/#\ \[RULES:BASTARDS\]/, @bastards)
|
24
|
-
@public = Port.new("#{@path}", :public).to_ipt
|
25
|
-
@content = @content.gsub(/#\ \[RULES:PUBLIC\]/, @public)
|
26
|
-
@allow = EyePee.new("#{@path}", :allow).to_ipt
|
27
|
-
@content = @content.gsub(/#\ \[RULES:ALLOWIP\]/, @allow)
|
28
|
-
@private = Port.new("#{@path}", :private).to_ipt
|
29
|
-
@content = @content.gsub(/#\ \[RULES:PRIVATE\]/, @private)
|
18
|
+
def generate
|
19
|
+
#generate_nat
|
20
|
+
generate_filter
|
21
|
+
end
|
30
22
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
23
|
+
def generate_filter
|
24
|
+
@dmz = Iface.new("#{@path}", :dmz).to_ipt
|
25
|
+
@content = @content.gsub(/#\ \[RULES:DMZ\]/, @dmz)
|
26
|
+
@bastards = EyePee.new("#{@path}", :deny).to_ipt
|
27
|
+
@content = @content.gsub(/#\ \[RULES:BASTARDS\]/, @bastards)
|
28
|
+
@public = Port.new("#{@path}", :public).to_ipt
|
29
|
+
@content = @content.gsub(/#\ \[RULES:PUBLIC\]/, @public)
|
30
|
+
@allow = EyePee.new("#{@path}", :allow).to_ipt
|
31
|
+
@content = @content.gsub(/#\ \[RULES:ALLOWIP\]/, @allow)
|
32
|
+
@private = Port.new("#{@path}", :private).to_ipt
|
33
|
+
@content = @content.gsub(/#\ \[RULES:PRIVATE\]/, @private)
|
38
34
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
# Make sure we are restoring what fail2ban has added
|
36
|
+
@f2b_chains = Jail.new.fail2ban_chains
|
37
|
+
@content = @content.gsub(/#\ \[CHAIN:FAIL2BAN\]/, @f2b_chains)
|
38
|
+
@f2b_rules = Jail.new.fail2ban_rules
|
39
|
+
@content = @content.gsub(/#\ \[RULES:FAIL2BAN\]/, @f2b_rules)
|
40
|
+
@jail = Jail.new.jail_rules
|
41
|
+
@content = @content.gsub(/#\ \[RULES:JAIL\]/, @jail)
|
42
|
+
|
43
|
+
# Remove the rest of the comments and extra lines
|
44
|
+
@content = @content.gsub(/^#.*$/, "")
|
45
|
+
@content = @content.gsub(/^$\n/, "")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
45
49
|
end
|
@@ -1,34 +1,34 @@
|
|
1
1
|
module Shutter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module IPTables
|
3
|
+
class EyePee
|
4
|
+
def initialize( path, state )
|
5
|
+
@state = state
|
6
|
+
file = File.open("#{path}/ip.#{state.to_s}", "r")
|
7
|
+
@content = file.read
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def to_s
|
11
|
+
@content
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
def to_ipt
|
15
|
+
@rules = ""
|
16
|
+
@content.each_line do |ip|
|
17
|
+
ip_clean = ip.strip
|
18
|
+
if ip_clean =~ /^[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}.[0-9]{1,3}(\/[0-9]{0,2})*$/
|
19
|
+
@rules += send(:"#{@state.to_s}_ipt", ip_clean)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
@rules
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def allow_ipt(ip)
|
26
|
+
"-A AllowIP -m state --state NEW -s #{ip} -j Allowed\n"
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
29
|
+
def deny_ipt(ip)
|
30
|
+
"-A Bastards -s #{ip} -j DropBastards\n"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
34
|
end
|
@@ -1,30 +1,30 @@
|
|
1
1
|
module Shutter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module IPTables
|
3
|
+
class Iface
|
4
|
+
def initialize( path, type )
|
5
|
+
@type = type
|
6
|
+
file = File.open("#{path}/iface.#{type.to_s}", "r")
|
7
|
+
@content = file.read
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def to_s
|
11
|
+
@content
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
14
|
+
def to_ipt
|
15
|
+
@rules = ""
|
16
|
+
@content.each_line do |line|
|
17
|
+
line = line.strip
|
18
|
+
if line =~ /^[a-z].+$/
|
19
|
+
@rules += send(:"#{@type.to_s}_ipt", line)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
@rules
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
25
|
+
def dmz_ipt( iface )
|
26
|
+
"-A Dmz -i #{iface} -j ACCEPT\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
30
|
end
|
@@ -1,26 +1,26 @@
|
|
1
1
|
module Shutter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
2
|
+
module IPTables
|
3
|
+
class Jail
|
4
|
+
def initialize( iptables = "/sbin/iptables")
|
5
|
+
@iptables = iptables
|
6
|
+
end
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def fail2ban_chains
|
9
|
+
`/sbin/iptables-save | grep "^:fail2ban"`
|
10
|
+
end
|
11
11
|
|
12
|
-
|
13
|
-
|
14
|
-
|
12
|
+
def fail2ban_rules
|
13
|
+
`/sbin/iptables-save | grep "^-A fail2ban"`
|
14
|
+
end
|
15
15
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
16
|
+
def jail_rules
|
17
|
+
jail = `/sbin/iptables-save | grep "^-A Jail"`
|
18
|
+
lines = jail.split('\n')
|
19
|
+
unless lines != [] && lines[-1] == "-A Jail -j RETURN\n"
|
20
|
+
jail += "-A Jail -j RETURN\n"
|
21
|
+
end
|
22
|
+
jail
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
26
|
end
|
@@ -1,35 +1,35 @@
|
|
1
1
|
module Shutter
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
2
|
+
module IPTables
|
3
|
+
class Port
|
4
|
+
def initialize( path, type )
|
5
|
+
@type = type
|
6
|
+
file = File.open("#{path}/ports.#{type.to_s}", "r")
|
7
|
+
@content = file.read
|
8
|
+
end
|
9
9
|
|
10
|
-
|
11
|
-
|
12
|
-
|
10
|
+
def to_s
|
11
|
+
@content
|
12
|
+
end
|
13
13
|
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
14
|
+
def to_ipt
|
15
|
+
@rules = ""
|
16
|
+
@content.each_line do |line|
|
17
|
+
line = line.strip
|
18
|
+
if line =~ /^[1-9].+$/
|
19
|
+
port,proto = line.split
|
20
|
+
@rules += send(:"#{@type.to_s}_ipt", port, proto)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
@rules
|
24
|
+
end
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
26
|
+
def private_ipt( port, proto )
|
27
|
+
"-A Private -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j RETURN\n"
|
28
|
+
end
|
29
29
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
30
|
+
def public_ipt( port, proto )
|
31
|
+
"-A Public -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j ACCEPT\n"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
35
|
end
|
data/lib/shutter/os.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
module Shutter
|
2
|
+
class OS
|
3
|
+
def initialize
|
4
|
+
unless File.exist?('/proc/version')
|
5
|
+
@version = "Unknown"
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
def family
|
10
|
+
@family ||= ENV['OS'] ? ENV['OS'] : RUBY_PLATFORM.split('-').last
|
11
|
+
end
|
12
|
+
|
13
|
+
def version
|
14
|
+
@version ||= IO.read('/proc/version')
|
15
|
+
end
|
16
|
+
|
17
|
+
def linux?
|
18
|
+
return family == "linux"
|
19
|
+
end
|
20
|
+
|
21
|
+
def dist
|
22
|
+
case version
|
23
|
+
when /Red Hat/
|
24
|
+
"RedHat"
|
25
|
+
when /Debian/
|
26
|
+
"Debian"
|
27
|
+
when /Ubuntu/
|
28
|
+
"Ubuntu"
|
29
|
+
else
|
30
|
+
"Unknown"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def redhat?
|
35
|
+
dist == "RedHat"
|
36
|
+
end
|
37
|
+
|
38
|
+
alias :centos? :redhat?
|
39
|
+
alias :fedora? :redhat?
|
40
|
+
end
|
41
|
+
end
|
data/lib/shutter/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: shutter
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- lib/shutter/iptables/iface.rb
|
35
35
|
- lib/shutter/iptables/jail.rb
|
36
36
|
- lib/shutter/iptables/port.rb
|
37
|
+
- lib/shutter/os.rb
|
37
38
|
- lib/shutter/version.rb
|
38
39
|
- shutter.gemspec
|
39
40
|
homepage: ''
|