diff --git a/src/sunstone/public/js/plugins/secgroups-tab.js b/src/sunstone/public/js/plugins/secgroups-tab.js
index 546571d473..2d90832ac4 100644
--- a/src/sunstone/public/js/plugins/secgroups-tab.js
+++ b/src/sunstone/public/js/plugins/secgroups-tab.js
@@ -389,36 +389,19 @@ var create_security_group_wizard_html =
\
\
\
\
@@ -918,7 +901,7 @@ function updateSecurityGroupInfo(request,security_group){
}
function insert_sg_rules_table(sg){
- var html =
+ var html =
'
\
\
\
diff --git a/src/vnm_mad/remotes/security_groups/SecurityGroups.rb b/src/vnm_mad/remotes/security_groups/SecurityGroups.rb
index 232927b07e..ec0e7444a3 100644
--- a/src/vnm_mad/remotes/security_groups/SecurityGroups.rb
+++ b/src/vnm_mad/remotes/security_groups/SecurityGroups.rb
@@ -14,6 +14,10 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
+# TODO: remove
+require 'rubygems'
+require 'pp'
+
################################################################################
# IP and NETMASK Library
################################################################################
@@ -201,11 +205,14 @@ end
# SecurityGroups and Rules
################################################################################
+# TODO: remove
+require 'colorator'
+
class CommandsError < StandardError; end
class Commands
def initialize
- @commands = []
+ clear!
end
def add(cmd)
@@ -228,18 +235,33 @@ class Commands
out = ""
@commands.each{|c|
- out << `#{c}`
+
+ # TODO: remove
+ puts "=> #{c}".green
+
+ c_out = `#{c}`
+ puts c_out if !c_out.empty?
+
+ out << c_out
if !$?.success?
- @commands = []
+ clear!
raise CommandsError.new(c), "Command Error: #{c}"
end
}
- @commands = []
+ clear!
out
end
+ def uniq!
+ @commands.uniq!
+ end
+
+ def clear!
+ @commands = []
+ end
+
def to_a
@commands
end
@@ -248,6 +270,25 @@ end
class RuleError < StandardError; end
class Rule
+ TYPES = {
+ # PROTOCOL, RULE_TYPE, NET, RANGE, ICMP_TYPE
+ [ 1, 1, 0, 0, 0 ] => :protocol,
+ [ 1, 1, 0, 1, 0 ] => :portrange,
+ [ 1, 1, 0, 0, 1 ] => :icmp_type,
+ [ 1, 1, 1, 0, 0 ] => :net,
+ [ 1, 1, 1, 1, 0 ] => :net_portrange,
+ [ 1, 1, 1, 0, 1 ] => :net_icmp_type
+ }
+
+ ICMP_TYPES = %w{3 5 11 12 0 4 8 9 10 13 14 17 18}
+
+ ICMP_TYPES_EXPANDED = {
+ 3 => [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15],
+ 5 => [0, 1, 2, 3],
+ 11 => [0, 1],
+ 12 => [0, 1]
+ }
+
def initialize(rule)
@rule = rule
@commands = Commands.new
@@ -273,7 +314,7 @@ class Rule
end
def range
- @rule[:range] || nil
+ @rule[:range]
end
def net
@@ -283,13 +324,31 @@ class Rule
r.get_nets.collect{|n| n.to_s}
end
+ def icmp_type
+ @rule[:icmp_type]
+ end
+
+ def icmp_type_expand
+ if (codes = ICMP_TYPES_EXPANDED[icmp_type.to_i])
+ codes.collect{|e| "#{icmp_type}/#{e}"}
+ else
+ ["#{icmp_type}/0"]
+ end
+ end
+
# Helper
def valid?
valid = true
error_message = []
- if !protocol || ![:tcp, :udp, :icmp, :esp].include?(protocol)
+ if type.nil?
+ error_message << "Invalid combination of rule attributes: "
+ error_message << type(true).to_s
+ valid = false
+ end
+
+ if !protocol || ![:all, :tcp, :udp, :icmp, :esp].include?(protocol)
error_message << "Invalid protocol: #{protocol}"
valid = false
end
@@ -304,8 +363,17 @@ class Rule
valid = false
end
- if range && protocol == :esp
- error_message << "IPSEC does not support port ranges"
+ if icmp_type && !ICMP_TYPES.include?(icmp_type)
+ error_message << "ICMP Type '#{icmp_type}' not supported. Valid list is '#{ICMP_TYPES.join(',')}'"
+ end
+
+ if icmp_type && !(protocol == :icmp)
+ error_message << "Protocol '#{protocol}' does not support ICMP TYPES"
+ valid = false
+ end
+
+ if range && ![:tcp, :udp].include?(protocol)
+ error_message << "Protocol '#{protocol}' does not support port ranges"
valid = false
end
@@ -321,6 +389,50 @@ class Rule
@rule[:ip].match(/^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$/) && \
@rule[:size].match(/^\d+$/)
end
+
+ # Returns the rule type. Rules currently support these (final and relevant)
+ # attributes.
+ #
+ # PROTOCOL (mandatory)
+ # - Specifies the protocol of the rule
+ # - values: ['ALL', 'TCP', 'UDP', 'ICMP', 'IPSEC']
+ #
+ # RULE_TYPE (mandatory)
+ # - Specifies the direction of application of the rule
+ # - values: ['INBOUND', 'OUTBOUND']
+ #
+ # RANGE (optional)
+ # - only works for protocols ['TCP', 'UDP']
+ # - uses the iptables multiports syntax
+ #
+ # ICMP_TYPE (optional)
+ # - Only works for protocol 'ICMP'
+ # - Is either in the form of '' or '/', where both
+ # '' and '' are integers. This class has a helper method
+ # tgat expands '' into all the '/' subtypes.
+ #
+ # IP and SIZE (optional but must be specified together)
+ # - Can be applied to any protocol
+ # - IP is the first valid IP and SIZE is the number of consecutive IPs
+ #
+ # Depending on the combination of these attributes we can obtaine 4 rule
+ # rule types (some with subtypes):
+ #
+ # ['PROTOCOL', 'RULE_TYPE'] => Type 1: 'protocol'
+ # ['PROTOCOL', 'RULE_TYPE', 'RANGE'] => Type 2A: 'portrange'
+ # ['PROTOCOL', 'RULE_TYPE', 'ICMP_TYPE'] => Type 2B: 'icmp_type'
+ # ['PROTOCOL', 'RULE_TYPE', 'IP', 'SIZE'] => Type 3: 'net'
+ # ['PROTOCOL', 'RULE_TYPE', 'IP', 'SIZE', 'RANGE'] => Type 4A: 'net_portrange'
+ # ['PROTOCOL', 'RULE_TYPE', 'IP', 'SIZE', 'ICMP_TYPE'] => Type 4B: 'net_icmp_type'
+ #
+ # @return [Symbol] The rule type
+ def type(only_key = false)
+ key = [protocol, rule_type, net, range, icmp_type].collect do |e|
+ !!e ? 1 : 0
+ end
+
+ only_key ? key : TYPES[key]
+ end
end
class SecurityGroup
@@ -343,93 +455,67 @@ end
################################################################################
class SecurityGroupIPTables < SecurityGroup
- # RULE_CLASS = IPTablesRule
+ GLOBAL_CHAIN = "opennebula"
def initialize(vm, nic, sg_id, rules)
super
@commands = Commands.new
- chain = "one-sg-#{@sg_id}"
+ @vars = self.class.vars(@vm, @nic, @sg_id)
- vm_id = @vm['ID']
- nic_id = @nic[:nic_id]
-
- @chain_in = "one-#{vm_id}-#{nic_id}-i"
- @chain_out = "one-#{vm_id}-#{nic_id}-o"
-
- @chain_sg_in = "one-#{vm_id}-#{nic_id}-#{@sg_id}-i"
- @chain_sg_out = "one-#{vm_id}-#{nic_id}-#{@sg_id}-o"
- end
-
- def bootstrap
- # SG chains
- @commands.iptables("-N #{@chain_sg_in}")
- @commands.iptables("-N #{@chain_sg_out}")
-
- # Redirect Traffic
- @commands.iptables("-A #{@chain_in} -j #{@chain_sg_in}")
- @commands.iptables("-A #{@chain_out} -j #{@chain_sg_out}")
-
- # IPsets
- @commands.ipset("create #{@chain_sg_in}-n hash:net")
- @commands.ipset("create #{@chain_sg_out}-n hash:net")
- @commands.ipset("create #{@chain_sg_in}-nr hash:net,port")
- @commands.ipset("create #{@chain_sg_out}-nr hash:net,port")
+ @chain_in = @vars[:chain_in]
+ @chain_out = @vars[:chain_out]
+ @set_sg_in = @vars[:set_sg_in]
+ @set_sg_out = @vars[:set_sg_out]
end
def process_rules
@rules.each do |rule|
- if !rule.range && !rule.net
- # T1 - protocol
+ case rule.type
+ when :protocol
+ chain = rule.rule_type == :inbound ? @chain_in : @chain_out
+ @commands.iptables("-A #{chain} -p #{rule.protocol} -j RETURN")
+
+ when :portrange
+ chain = rule.rule_type == :inbound ? @chain_in : @chain_out
+ @commands.iptables("-A #{chain} -p #{rule.protocol} -m multiport --dports #{rule.range} -j RETURN")
+
+ when :icmp_type
+ chain = rule.rule_type == :inbound ? @chain_in : @chain_out
+ @commands.iptables("-A #{chain} -p icmp --icmp-type #{rule.icmp_type} -j RETURN")
+
+ when :net
if rule.rule_type == :inbound
- chain = @chain_sg_in
+ chain = @chain_in
+ set = "#{@set_sg_in}-#{rule.protocol}-n"
+ dir = "src"
else
- chain = @chain_sg_out
+ chain = @chain_out
+ set = "#{@set_sg_out}-#{rule.protocol}-n"
+ dir = "dst"
end
- @commands.iptables("-A #{chain} -p #{rule.protocol} -j ACCEPT")
-
- elsif rule.range && !rule.net
- # T2 - port range
- if rule.rule_type == :inbound
- chain = @chain_sg_in
- else
- chain = @chain_sg_out
- end
- @commands.iptables("-A #{chain} -p #{rule.protocol} -m multiport --dports #{rule.range} -j ACCEPT")
-
- elsif !rule.range && rule.net
- # T3 - net
- if rule.rule_type == :inbound
- chain = @chain_sg_in
- dir = "src"
- else
- chain = @chain_sg_out
- dir = "dst"
- end
-
- set = "#{chain}-n"
-
- @commands.iptables("-A #{chain} -p #{rule.protocol} -m set --match-set #{set} #{dir} -j ACCEPT")
+ @commands.ipset("create #{set} hash:net")
+ @commands.iptables("-A #{chain} -p #{rule.protocol} -m set --match-set #{set} #{dir} -j RETURN")
rule.net.each do |n|
@commands.ipset("add -exist #{set} #{n}")
end
- elsif rule.range && rule.net
- # T4 - net && port range
+ when :net_portrange
if rule.rule_type == :inbound
- chain = @chain_sg_in
+ chain = @chain_in
+ set = @set_sg_in + "-nr"
dir = "src,dst"
else
- chain = @chain_sg_out
+ chain = @chain_in
+ set = @set_sg_in + "-n"
dir = "dst,dst"
end
- set = "#{chain}-nr"
-
- @commands.iptables("-A #{chain} -m set --match-set #{set} #{dir} -j ACCEPT")
+ @commands.ipset("create #{set} hash:net,port")
+ @commands.iptables("-A #{chain} -m set --match-set #{set} #{dir} -j RETURN")
rule.net.each do |n|
rule.range.split(",").each do |r|
@@ -438,13 +524,184 @@ class SecurityGroupIPTables < SecurityGroup
@commands.ipset("add -exist #{set} #{net_range}")
end
end
+
+ when :net_icmp_type
+ if rule.rule_type == :inbound
+ chain = @chain_in
+ set = @set_sg_in + "-nr"
+ dir = "src,dst"
+ else
+ chain = @chain_in
+ set = @set_sg_in + "-n"
+ dir = "dst,dst"
+ end
+
+ @commands.ipset("create #{set} hash:net,port")
+ @commands.iptables("-A #{chain} -m set --match-set #{set} #{dir} -j RETURN")
+
+ rule.net.each do |n|
+ rule.icmp_type_expand.each do |type_code|
+ net_range = "#{n},icmp:#{type_code}"
+ @commands.ipset("add -exist #{set} #{net_range}")
+ end if rule.icmp_type_expand
+ end
end
end
+
+ @commands.uniq!
end
def run!
@commands.run!
end
+
+ ############################################################################
+ # Static methods
+ ############################################################################
+
+ def self.global_bootstrap
+ info = self.info
+
+ if !info[:iptables_s].split("\n").include? "-N #{GLOBAL_CHAIN}"
+ commands = Commands.new
+
+ commands.iptables "-N #{GLOBAL_CHAIN}"
+ commands.iptables "-A FORWARD -m physdev --physdev-is-bridged -j #{GLOBAL_CHAIN}"
+ commands.iptables "-A #{GLOBAL_CHAIN} -j DROP"
+
+ commands.run!
+ end
+ end
+
+ def self.nic_pre(vm, nic)
+ commands = Commands.new
+
+ vars = self.vars(vm, nic)
+
+ chain = vars[:chain]
+ chain_in = vars[:chain_in]
+ chain_out = vars[:chain_out]
+
+ # create chains
+ commands.iptables "-N #{chain_in}" # inbound
+ commands.iptables "-N #{chain_out}" # outbound
+
+ # Send traffic to the NIC chains
+ commands.iptables"-I #{GLOBAL_CHAIN} -m physdev --physdev-out #{nic[:tap]} --physdev-is-bridged -j #{chain_in}"
+ commands.iptables"-I #{GLOBAL_CHAIN} -m physdev --physdev-in #{nic[:tap]} --physdev-is-bridged -j #{chain_out}"
+
+ # Mac-spofing
+ if nic[:filter_mac_spoofing]
+ commands.iptables"-A #{chain_out} -m mac ! --mac-source #{nic[:mac]} -j DROP"
+ end
+
+ # IP-spofing
+ if nic[:filter_ip_spoofing]
+ commands.iptables"-A #{chain_out} ! --source #{nic[:ip]} -j DROP"
+ end
+
+ # Related, Established
+ commands.iptables"-A #{chain_in} -m state --state ESTABLISHED,RELATED -j ACCEPT"
+ commands.iptables"-A #{chain_out} -m state --state ESTABLISHED,RELATED -j ACCEPT"
+
+ commands.run!
+ end
+
+ def self.nic_post(vm, nic)
+ vars = self.vars(vm, nic)
+ chain_in = vars[:chain_in]
+ chain_out = vars[:chain_out]
+
+ commands = Commands.new
+ commands.iptables("-A #{chain_in} -j DROP")
+ commands.iptables("-A #{chain_out} -j DROP")
+
+ commands.run!
+ end
+
+ def self.nic_deactivate(vm, nic)
+ vars = self.vars(vm, nic)
+ chain = vars[:chain]
+ chain_in = vars[:chain_in]
+ chain_out = vars[:chain_out]
+
+ info = self.info
+ iptables_forwards = info[:iptables_forwards]
+ iptables_s = info[:iptables_s]
+ ipset_list = info[:ipset_list]
+
+ commands = Commands.new
+
+ iptables_forwards.lines.reverse_each do |line|
+ fields = line.split
+ if [chain_in, chain_out].include?(fields[1])
+ n = fields[0]
+ commands.iptables("-D #{GLOBAL_CHAIN} #{n}")
+ end
+ end
+
+ remove_chains = []
+ iptables_s.lines.each do |line|
+ if line.match(/^-N #{chain}/)
+ remove_chains << line.split[1]
+ end
+ end
+ remove_chains.each {|c| commands.iptables("-F #{c}") }
+ remove_chains.each {|c| commands.iptables("-X #{c}") }
+
+ ipset_list.lines.each do |line|
+ if line.match(/^#{chain}/)
+ set = line.strip
+ commands.ipset("destroy #{set}")
+ end
+ end
+
+ commands.run!
+ end
+
+ def self.info
+ commands = Commands.new
+
+ commands.iptables("-S")
+ iptables_s = commands.run!
+
+ if iptables_s.match(/^-N #{GLOBAL_CHAIN}$/)
+ commands.iptables("-L #{GLOBAL_CHAIN} --line-numbers")
+ iptables_forwards = commands.run!
+ else
+ iptables_forwards = ""
+ end
+
+ commands.ipset("list -name")
+ ipset_list = commands.run!
+
+ {
+ :iptables_forwards => iptables_forwards,
+ :iptables_s => iptables_s,
+ :ipset_list => ipset_list
+ }
+ end
+
+
+ def self.vars(vm, nic, sg_id = nil)
+ vm_id = vm['ID']
+ nic_id = nic[:nic_id]
+
+ vars = {}
+
+ vars[:vm_id] = vm_id,
+ vars[:nic_id] = nic_id,
+ vars[:chain] = "one-#{vm_id}-#{nic_id}",
+ vars[:chain_in] = "#{vars[:chain]}-i",
+ vars[:chain_out] = "#{vars[:chain]}-o"
+
+ if sg_id
+ vars[:set_sg_in] = "#{vars[:chain]}-#{sg_id}-i"
+ vars[:set_sg_out] = "#{vars[:chain]}-#{sg_id}-o"
+ end
+
+ vars
+ end
end
################################################################################
@@ -461,23 +718,19 @@ end
class OpenNebulaSG < OpenNebulaNetwork
DRIVER = "sg"
- XPATH_FILTER = "TEMPLATE/NIC[SECURITY_GROUPS]"
+ XPATH_FILTER = "TEMPLATE/NIC"
SECURITY_GROUP_CLASS = SecurityGroupIPTables
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm, XPATH_FILTER, deploy_id, hypervisor)
@locking = true
+ @commands = Commands.new
+
+ get_security_group_rules
end
- def activate
- deactivate
-
- lock
-
- vm_id = @vm['ID']
-
- security_group_rules = {}
-
+ def get_security_group_rules
+ rules = {}
@vm.vm_root.elements.each('TEMPLATE/SECURITY_GROUP_RULE') do |r|
security_group_rule = {}
@@ -487,51 +740,35 @@ class OpenNebulaSG < OpenNebulaNetwork
end
id = security_group_rule[:security_group_id]
-
- security_group_rules[id] = [] if security_group_rules[id].nil?
-
- security_group_rules[id] << security_group_rule
+ rules[id] = [] if rules[id].nil?
+ rules[id] << security_group_rule
end
+ @security_group_rules = rules
+ end
+ def activate
+ deactivate
+ lock
+
+ # Global Bootstrap
+ SECURITY_GROUP_CLASS.global_bootstrap
+
+ # Process the rules
@vm.nics.each do |nic|
- commands = Commands.new
+ next if nic[:security_groups].nil? \
+ && nic[:filter_mac_spoofing].nil? \
+ && nic[:filter_ip_spoofing].nil?
- nic_id = nic[:nic_id]
- chain = "one-#{vm_id}-#{nic_id}"
- chain_in = "#{chain}-i"
- chain_out = "#{chain}-o"
-
- # create nic chains
- commands.iptables("-N #{chain_in}") # inbound
- commands.iptables("-N #{chain_out}") # outbound
-
- # Send traffic to the NIC chains
- commands.iptables("-A FORWARD -m physdev --physdev-out #{nic[:tap]} --physdev-is-bridged -j #{chain_in}")
- commands.iptables("-A FORWARD -m physdev --physdev-in #{nic[:tap]} --physdev-is-bridged -j #{chain_out}")
-
- # Related, Established
- commands.iptables("-A #{chain_in} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
- commands.iptables("-A #{chain_out} -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT")
-
- begin
- commands.run!
- rescue Exception => e
- unlock
- raise OpenNebulaSGError.new(:bootstrap, e)
- end
+ SECURITY_GROUP_CLASS.nic_pre(@vm, nic)
sg_ids = nic[:security_groups].split(",")
-
sg_ids.each do |sg_id|
- rules = security_group_rules[sg_id]
-
+ rules = @security_group_rules[sg_id]
sg = SECURITY_GROUP_CLASS.new(@vm, nic, sg_id, rules)
- sg.bootstrap
- sg.process_rules
-
begin
+ sg.process_rules
sg.run!
rescue Exception => e
unlock
@@ -539,10 +776,7 @@ class OpenNebulaSG < OpenNebulaNetwork
end
end
- commands.iptables("-A #{chain_in} -j DROP") # inbound
- commands.iptables("-A #{chain_out} -j DROP") # outbound
-
- commands.run!
+ SECURITY_GROUP_CLASS.nic_post(@vm, nic)
end
unlock
@@ -551,58 +785,14 @@ class OpenNebulaSG < OpenNebulaNetwork
def deactivate
lock
- vm_id = @vm['ID']
-
- @vm.nics.each do |nic|
- commands = Commands.new
-
- nic_id = nic[:nic_id]
-
- chain = "one-#{vm_id}-#{nic_id}"
- chain_in = "#{chain}-i"
- chain_out = "#{chain}-o"
-
- # remove everything
- begin
- commands.iptables("-L FORWARD --line-numbers")
- iptables_forwards = commands.run!
-
- commands.iptables("-S")
- iptables_s = commands.run!
-
- commands.ipset("list -name")
- ipset_list = commands.run!
-
- iptables_forwards.lines.reverse.each do |line|
- fields = line.split
- if [chain_in, chain_out].include?(fields[1])
- n = fields[0]
- commands.iptables("-D FORWARD #{n}")
- end
- end
-
- remove_chains = []
- iptables_s.lines.each do |line|
- if line.match(/^-N #{chain}/)
- remove_chains << line.split[1]
- end
- end
- remove_chains.each {|c| commands.iptables("-F #{c}") }
- remove_chains.each {|c| commands.iptables("-X #{c}") }
-
- ipset_list.lines.each do |line|
- if line.match(/^#{chain}/)
- set = line.strip
- commands.ipset("destroy #{set}")
- end
- end
-
- commands.run!
- rescue Exception => e
- raise OpenNebulaSGError.new(:deactivate, e)
+ begin
+ @vm.nics.each do |nic|
+ SECURITY_GROUP_CLASS.nic_deactivate(@vm, nic)
end
+ rescue Exception => e
+ raise OpenNebulaSGError.new(:deactivate, e)
+ ensure
+ unlock
end
-
- unlock
end
end