lib/shutter/iptables.rb in shutter-0.0.7 vs lib/shutter/iptables.rb in shutter-0.1.0

- old
+ new

@@ -1,13 +1,218 @@ -require 'shutter/iptables/base' -require 'shutter/iptables/eyepee' -require 'shutter/iptables/iface' -require 'shutter/iptables/jail' -require 'shutter/iptables/port' -require 'shutter/iptables/forward' -require 'shutter/os' - module Shutter - module IPTables - IPTABLES_RESTORE="/sbin/iptables-restore" + module Firewall + class IPTables + RULES_DMZ_BLOCK = "# [RULES:DMZ]" + RULES_FORWARD_BLOCK = "# [RULES:FORWARD]" + RULES_POSTROUTING_BLOCK = "# [RULES:POSTROUTING]" + RULES_BASTARDS_BLOCK = "# [RULES:BASTARDS]" + RULES_PUBLIC_BLOCK = "# [RULES:PUBLIC]" + RULES_ALLOWIP_BLOCK = "# [RULES:ALLOWIP]" + RULES_PRIVATE_BLOCK = "# [RULES:PRIVATE]" + RULES_FAIL2BAN_BLOCK = "# [RULES:FAIL2BAN]" + RULES_JAIL_BLOCK = "# [RULES:JAIL]" + CHAIN_FAIL2BAN_BLOCK = "# [CHAIN:FAIL2BAN]" + + def initialize(path) + @path = path + @base = read("base.ipt",false).join("\n") + @iface_forward = read("iface.forward") + @ports_private = read("ports.private") + @ports_public = read("ports.public") + @ip_allow = read("ip.allow") + @ip_deny = read("ip.deny") + @dmz_device = read("iface.dmz") + @os = Shutter::OS.new + end + + def base_sub(block,content) + @base = @base.gsub(/#{Regexp.quote(block)}/, content) + end + + def generate + base_sub(RULES_DMZ_BLOCK, dmz_device_block) + base_sub(RULES_FORWARD_BLOCK, forward_block) + base_sub(RULES_POSTROUTING_BLOCK, postrouting_block) + base_sub(RULES_BASTARDS_BLOCK, deny_ip_block) + base_sub(RULES_PUBLIC_BLOCK, allow_public_port_block) + base_sub(RULES_ALLOWIP_BLOCK, allow_ip_block) + base_sub(RULES_PRIVATE_BLOCK, allow_private_port_block) + base_sub(RULES_FAIL2BAN_BLOCK, fail2ban_rules_block) + base_sub(RULES_JAIL_BLOCK, jail_rules_block) + base_sub(CHAIN_FAIL2BAN_BLOCK, fail2ban_chains_block) + clean + end + + def to_s + @base + end + + def clean + @base = @base.gsub(/^#.*$/, "") + @base = @base.gsub(/^$\n/, "") + end + + def read(file, filter=true) + #puts "Reading: #{@path}/#{file}" + lines = File.read("#{@path}/#{file}").split("\n") + # Doesn't work with 1.8.x + # lines.keep_if{ |line| line =~ /^[a-z0-9].+$/ } if filter + # so since we are iterating through this, well handle the stripping as well + # lines.map { |line| line.strip } + newlines = [] + lines.each do |line| + if filter + newlines << line.strip if line =~ /^[a-z0-9].+$/ + else + newlines << line.strip + end + end + newlines + end + + def save + puts self.generate + end + + def restore + IO.popen("#{iptables_restore}", "r+") do |iptr| + iptr.puts self.generate ; iptr.close_write + end + end + + def persist(pfile) + File.open(pfile, "w") do |f| + f.write(@base) + end + end + + ### + ### IPTables Commands + ### + def iptables_save + @iptable_save ||= `"#{@os.iptables_save}"` + end + + def iptables_restore + "#{@os.iptables_restore}" + end + + ### + ### Block Generation + ### + def forward_block + content = "" + @iface_forward.each do |line| + src, dst = line.split(' ') + content += self.forward_content(src,dst) + end + content + end + + def postrouting_block + masq_ifaces = [] + content = "" + @iface_forward.each do |line| + src, dst = line.split(' ') + content += self.postrouting_content(dst) unless masq_ifaces.include?(dst) + masq_ifaces << dst + end + content + end + + def allow_private_port_block + content = "" + @ports_private.each do |line| + port,proto = line.split + content += self.allow_private_port_content(port, proto) + end + content + end + + def allow_public_port_block + content = "" + @ports_public.each do |line| + port,proto = line.split + raise "Invalid port in port.allow" unless port =~ /^[0-9].*$/ + raise "Invalid protocol in port.allow" unless proto =~ /^(tcp|udp)$/ + content += self.allow_public_port_content(port, proto) + end + content + end + + def allow_ip_block + content = "" + @ip_allow.each do |line| + 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})*$/ + content += self.allow_ip_content(line) + end + content + end + + def deny_ip_block + content = "" + @ip_deny.each do |line| + 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})*$/ + content += self.deny_ip_content(line) + end + content + end + + def dmz_device_block + content = "" + @dmz_device.each do |line| + raise "Invalid device in iface.dmz" unless line =~ /^[a-z][a-z0-9].*$/ + content += self.dmz_device_content(line) + end + content + end + + def fail2ban_chains_block + iptables_save.scan(/^:fail2ban.*$/).join("\n") + end + + def fail2ban_rules_block + iptables_save.scan(/^-A fail2ban.*$/).join("\n") + end + + def jail_rules_block + lines = iptables_save.scan(/^-A Jail.*$/) + lines << "-A Jail -j RETURN\n" unless lines.last =~ /-A Jail -j RETURN/ + lines.join("\n") + end + + ### + ### Block Content + ### + def forward_content(src,dst) + rule = "-A FORWARD -i #{src} -o #{dst} -m state --state NEW,RELATED,ESTABLISHED -j ACCEPT\n" + rule += "-A FORWARD -i #{dst} -o #{src} -m state --state RELATED,ESTABLISHED -j ACCEPT\n" + rule + end + + def postrouting_content(iface) + "-A POSTROUTING -o #{iface} -j MASQUERADE\n" + end + + def allow_private_port_content(port, proto) + "-A Private -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j RETURN\n" + end + + def allow_public_port_content(port, proto) + "-A Public -m state --state NEW -p #{proto} -m #{proto} --dport #{port} -j ACCEPT\n" + end + + def allow_ip_content(ip) + "-A AllowIP -m state --state NEW -s #{ip} -j Allowed\n" + end + + def deny_ip_content(ip) + "-A Bastards -s #{ip} -j DropBastards\n" + end + + def dmz_device_content(iface) + "-A Dmz -i #{iface} -j ACCEPT\n" + end + + end end end \ No newline at end of file