1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-01-11 05:17:41 +03:00

feature #3175: Moved firewall and SG drivers to new module. Removed old files

This commit is contained in:
Ruben S. Montero 2014-12-22 19:10:30 +01:00
parent fba4deec6e
commit 536be6ecf0
9 changed files with 319 additions and 1369 deletions

View File

@ -932,12 +932,16 @@ AUTH_PLAIN_FILES="src/authm_mad/remotes/plain/authenticate"
# Virtual Network Manager drivers to be installed under $REMOTES_LOCATION/vnm
#-------------------------------------------------------------------------------
NETWORK_FILES="src/vnm_mad/remotes/OpenNebulaNetwork.rb \
NETWORK_FILES="src/vnm_mad/remotes/lib/opennebula_network.rb \
src/vnm_mad/remotes/OpenNebulaNetwork.conf \
src/vnm_mad/remotes/Firewall.rb \
src/vnm_mad/remotes/SecurityGroups.rb \
src/vnm_mad/remotes/IPNetmask.rb \
src/vnm_mad/remotes/OpenNebulaNic.rb"
src/vnm_mad/remotes/lib/one_firewall.rb \
src/vnm_mad/remotes/lib/one_sg.rb \
src/vnm_mad/remotes/lib/address.rb \
src/vnm_mad/remotes/lib/command.rb \
src/vnm_mad/remotes/lib/command.rb \
src/vnm_mad/remotes/lib/security_groups.rb \
src/vnm_mad/remotes/lib/security_groups_iptables.rb \
src/vnm_mad/remotes/lib/nic.rb"
NETWORK_8021Q_FILES="src/vnm_mad/remotes/802.1Q/clean \
src/vnm_mad/remotes/802.1Q/post \

View File

@ -1,180 +0,0 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
class OpenNebulaFirewall < OpenNebulaNetwork
DRIVER = "fw"
XPATH_FILTER = "TEMPLATE/NIC[ICMP|WHITE_PORTS_TCP|WHITE_PORTS_UDP|" <<
"BLACK_PORTS_TCP|BLACK_PORTS_UDP]"
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm,XPATH_FILTER,deploy_id,hypervisor)
@locking = true
end
def activate
lock
vm_id = @vm['ID']
process do |nic|
#:white_ports_tcp => iptables_range
#:white_ports_udp => iptables_range
#:black_ports_tcp => iptables_range
#:black_ports_udp => iptables_range
#:icmp => 'DROP' or 'NO'
nic_rules = Array.new
chain = "one-#{vm_id}-#{nic[:network_id]}"
tap = nic[:tap]
next if chain_exists?(chain)
if tap
#TCP
if range = nic[:white_ports_tcp]
nic_rules << filter_established(chain, :tcp, :accept)
nic_rules << filter_ports(chain, :tcp, range, :accept)
nic_rules << filter_protocol(chain, :tcp, :drop)
elsif range = nic[:black_ports_tcp]
nic_rules << filter_ports(chain, :tcp, range, :drop)
end
#UDP
if range = nic[:white_ports_udp]
nic_rules << filter_established(chain, :udp, :accept)
nic_rules << filter_ports(chain, :udp, range, :accept)
nic_rules << filter_protocol(chain, :udp, :drop)
elsif range = nic[:black_ports_udp]
nic_rules << filter_ports(chain, :udp, range, :drop)
end
#ICMP
if nic[:icmp]
if %w(no drop).include? nic[:icmp].downcase
nic_rules << filter_established(chain, :icmp, :accept)
nic_rules << filter_protocol(chain, :icmp, :drop)
end
end
process_chain(chain, tap, nic_rules)
end
end
unlock
end
def deactivate
lock
vm_id = @vm['ID']
process do |nic|
chain = "one-#{vm_id}-#{nic[:network_id]}"
iptables_out = `#{COMMANDS[:iptables]} -n -v --line-numbers -L FORWARD`
if m = iptables_out.match(/.*#{chain}.*/)
rule_num = m[0].split(/\s+/)[0]
purge_chain(chain, rule_num)
end
end
unlock
end
def purge_chain(chain, rule_num)
rules = Array.new
rules << rule("-D FORWARD #{rule_num}")
rules << rule("-F #{chain}")
rules << rule("-X #{chain}")
run_rules rules
end
def process_chain(chain, tap, nic_rules)
rules = Array.new
if !nic_rules.empty?
# new chain
rules << new_chain(chain)
# move tap traffic to chain
rules << tap_to_chain(tap, chain)
rules << nic_rules
end
run_rules rules
end
def filter_established(chain, protocol, policy)
policy = policy.to_s.upcase
rule "-A #{chain} -p #{protocol} -m state --state ESTABLISHED -j #{policy}"
end
def run_rules(rules)
rules.flatten.each do |rule|
OpenNebula.exec_and_log(rule)
end
end
def range?(range)
range.match(/^(?:(?:\d+|\d+:\d+),)*(?:\d+|\d+:\d+)$/)
end
def filter_protocol(chain, protocol, policy)
policy = policy.to_s.upcase
rule "-A #{chain} -p #{protocol} -j #{policy}"
end
def filter_ports(chain, protocol, range, policy)
policy = policy.to_s.upcase
range.gsub!(/\s+/,"")
if range? range
rule "-A #{chain} -p #{protocol} -m multiport --dports #{range} -j #{policy}"
end
end
def tap_to_chain(tap, chain)
iptables_out = `#{COMMANDS[:iptables]} -n -v --line-numbers -L FORWARD`
# Insert the rule on top of the 'opennebula' chain if it exists, so it
# doesn't conflict with the security groups driver
index = nil
iptables_out.lines.each do |line|
fields = line.split
if fields.include?("opennebula") && fields.include?("--physdev-is-bridged")
index = fields[0]
break
end
end
if index
rule "-I FORWARD #{index} -m physdev --physdev-out #{tap} -j #{chain}"
else
rule "-A FORWARD -m physdev --physdev-out #{tap} -j #{chain}"
end
end
def new_chain(chain)
rule "-N #{chain}"
end
def chain_exists?(chain)
iptables_nl =`#{COMMANDS[:iptables]} -nL`
chains = iptables_nl.scan(/(one-.*?) .*references/).flatten
chains.include? chain
end
def rule(rule)
"#{COMMANDS[:iptables]} #{rule}"
end
end

View File

@ -1,198 +0,0 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
################################################################################
# IP and NETMASK Library
################################################################################
class IP
include Comparable
attr_accessor :ip
def initialize(ip)
@ip = ip
end
def to_s
@ip
end
def to_i
@ip.split(".").inject(0) {|t,e| (t << 8) + e.to_i }
end
def to_hex
"0x" + to_i.to_s(16).rjust(8, '0')
end
def to_bin
"0b" + to_i.to_s(2).rjust(16, '0')
end
def to_hex_groups(p="")
to_i.to_s(16).rjust(8, '0').scan(/.{2}/).collect{|e| p+e}.join('.')
end
def to_bin_groups(p="")
to_i.to_s(2).rjust(16, '0').scan(/.{8}/).collect{|e| p+e}.join('.')
end
def self.from_i(i)
ip = 3.downto(0).collect {|s| (i >> 8*s) & 0xff }.join('.')
self.new(ip)
end
def &(another_ip)
IP.from_i(self.to_i & another_ip.to_i)
end
def +(size)
IP.from_i(self.to_i + size)
end
def -(e)
if e.instance_of? Fixnum
IP.from_i(self.to_i - e)
else
e.to_i - self.to_i
end
end
def <=>(another_ip)
self.to_i <=> another_ip.to_i
end
end
class Netmask < IP
def self.from_cidr(cidr)
self.from_i(0xffffffff ^ 2**(32-cidr)-1)
end
def to_cidr
32 - Math.log((to_i ^ 0xffffffff) + 1, 2).to_i
end
end
class Net
attr_accessor :ip, :netmask
def initialize(ip, netmask = nil)
if netmask
@ip, @netmask = ip, netmask
else
ip, netmask = ip.split('/')
@ip = IP.new(ip)
@netmask = Netmask.from_cidr(netmask.to_i) if netmask
end
@network_address = network_address
@last_address = last_address
end
def network_address
IP.from_i(@ip.to_i & @netmask.to_i)
end
def last_address
IP.from_i(@ip.to_i | (@netmask.to_i ^ 0xffffffff))
end
def info
s = ""
s << @network_address.to_s.ljust(15)
s << " /"
s << @netmask.to_cidr.to_s.rjust(2)
s << " "
s << @network_address.to_s.ljust(15)
s << " "
s << last_address.to_s.ljust(15)
s
end
def to_s
"#{@network_address}/#{@netmask.to_cidr}"
end
def next_net
next_ip = IP.from_i(last_address.to_i + 1)
Net.new(next_ip, @netmask)
end
def between?(ip_start, ip_end)
network_address >= ip_start && last_address <= ip_end
end
end
class Range
def initialize(ip_start, size)
@ip_start = IP.new(ip_start)
@ip_end = @ip_start + size
end
def get_nets
self.class.get_nets(@ip_start, @ip_end)
end
def largest_subnet
self.class.largest_subnet(@ip_start, @ip_end)
end
def self.get_nets(ip_start, ip_end)
nets = []
net_m = largest_subnet(ip_start, ip_end)
# left scraps
if ip_start < net_m.network_address
nets.concat get_nets(ip_start, net_m.network_address - 1)
end
nets << net_m
# right scraps
if net_m.last_address < ip_end
nets.concat get_nets(net_m.last_address + 1, ip_end)
end
nets
end
def self.largest_subnet(ip_start, ip_end)
size = ip_start - ip_end
# start with the largest subnet
if size > 0
cidr = 32 - Math.log(size, 2).floor
else
cidr = 32
end
fits = false
while !fits
net = Net.new(ip_start, Netmask.from_cidr(cidr))
net = net.next_net if ip_start > net.network_address
cidr += 1
break if cidr > 32
fits = net.between?(ip_start, ip_end)
end
net
end
end

View File

@ -1,248 +0,0 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), '..')
require 'rexml/document'
require 'base64'
require 'yaml'
require 'OpenNebulaNic'
require 'scripts_common'
include OpenNebula
begin
CONF = YAML.load_file(
File.join(File.dirname(__FILE__), "OpenNebulaNetwork.conf")
)
rescue
CONF = {
:start_vlan => 2
}
end
def get_xen_command
if system("ps axuww | grep -v grep | grep '\\bxen\\b'")
"sudo xm"
else
"sudo xl"
end
end
COMMANDS = {
:ebtables => "sudo ebtables",
:iptables => "sudo iptables",
:brctl => "sudo brctl",
:ip => "sudo ip",
:virsh => "virsh -c qemu:///system",
:xm => get_xen_command,
:ovs_vsctl=> "sudo ovs-vsctl",
:ovs_ofctl=> "sudo ovs-ofctl",
:lsmod => "lsmod",
:ipset => "sudo ipset"
}
# Set PATH
ENV['PATH'] = "#{ENV['PATH']}:/bin:/sbin:/usr/bin"
class VM
attr_accessor :nics, :vm_info, :deploy_id, :vm_root
def nic_build_hash(nic_element,nic)
nic_element.elements.each('*') do |nic_attribute|
key = nic_attribute.name.downcase.to_sym
if nic_attribute.has_elements?
data = {}
nic_build_hash(nic_attribute,data)
else
data = nic_attribute.text
end
if nic[key]
if nic[key].instance_of?(Array)
nic[key] << data
else
nic[key] = [nic[key], data]
end
else
nic[key] = data
end
end
end
def initialize(vm_root, xpath_filter, deploy_id, hypervisor)
@vm_root = vm_root
@xpath_filter = xpath_filter
@deploy_id = deploy_id
@hypervisor = hypervisor
@vm_info = Hash.new
@deploy_id = nil if deploy_id == "-"
nics = Nics.new(@hypervisor)
@vm_root.elements.each(@xpath_filter) do |nic_element|
nic = nics.new_nic
nic_build_hash(nic_element,nic)
nic.get_info(self)
nic.get_tap(self)
nics << nic
end
@nics = nics
end
def each_nic(block)
if @nics != nil
@nics.each do |the_nic|
block.call(the_nic)
end
end
end
def [](element)
if @vm_root
val = @vm_root.elements[element]
if !val.nil? and val.text
return val.text
end
end
nil
end
end
class OpenNebulaNetwork
attr_reader :hypervisor, :vm
def self.from_base64(vm_64, deploy_id = nil, hypervisor = nil)
vm_xml = Base64::decode64(vm_64)
self.new(vm_xml, deploy_id, hypervisor)
end
def self.filter_driver(vm_64, deploy_id = nil, hypervisor = nil)
vm_xml = Base64::decode64(vm_64)
if self.has_fw_attrs?(vm_xml)
OpenNebulaFirewall.new(vm_xml, deploy_id, hypervisor)
else
OpenNebulaSG.new(vm_xml, deploy_id, hypervisor)
end
end
def initialize(vm_tpl, xpath_filter, deploy_id = nil, hypervisor = nil)
@locking = false
if !hypervisor
@hypervisor = detect_hypervisor
else
@hypervisor = hypervisor
end
@vm = VM.new(REXML::Document.new(vm_tpl).root, xpath_filter, deploy_id, @hypervisor)
end
def lock
if @locking
driver_name = self.class.name.downcase
@locking_file = File.open("/tmp/onevnm-#{driver_name}-lock","w")
@locking_file.flock(File::LOCK_EX)
end
end
def unlock
if @locking
@locking_file.close
end
end
def process(&block)
@vm.each_nic(block)
end
def detect_hypervisor
lsmod = `#{COMMANDS[:lsmod]}`
xen_file = "/proc/xen/capabilities"
if File.exists?(xen_file)
"xen"
elsif lsmod.match(/kvm/)
"kvm"
else
nil
end
end
def get_interfaces
bridges = Hash.new
brctl_exit =`#{COMMANDS[:brctl]} show`
cur_bridge = ""
brctl_exit.split("\n")[1..-1].each do |l|
l = l.split
if l.length > 1
cur_bridge = l[0]
bridges[cur_bridge] = Array.new
bridges[cur_bridge] << l[3] if l[3]
else
bridges[cur_bridge] << l[0]
end
end
bridges
end
end
# Dynamic factory method for the filter class
require 'Firewall'
require 'SecurityGroups'
class OpenNebulaNetwork
# Returns a filter object based on the contents of the template
#
# @return OpenNebulaFirewall or OpenNebulaSG object
def self.filter_driver(vm_64, deploy_id = nil, hypervisor = nil)
vm_xml = Base64::decode64(vm_64)
if self.has_fw_attrs?(vm_xml)
OpenNebulaFirewall.new(vm_xml, deploy_id, hypervisor)
else
OpenNebulaSG.new(vm_xml, deploy_id, hypervisor)
end
end
# Returns true if the template contains the deprecated firewall attributes:
# - ICMP
# - WHITE_PORTS_TCP
# - WHITE_PORTS_UDP
# - BLACK_PORTS_TCP
# - BLACK_PORTS_UDP
#
# @return Boolean
def self.has_fw_attrs?(vm_xml)
vm_root = REXML::Document.new(vm_xml).root
!vm_root.elements[OpenNebulaFirewall::XPATH_FILTER].nil?
end
end

View File

@ -1,136 +0,0 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
# This class represents the NICS of a VM
class Nics < Array
def initialize(hypervisor)
case hypervisor
when "kvm"
@nicClass = NicKVM
when "xen"
@nicClass = NicXen
when "vmware"
@nicClass = NicVMware
end
end
def new_nic
@nicClass.new
end
end
# A NIC using KVM. This class implements functions to get the physical interface
# that the NIC is using
class NicKVM < Hash
def initialize
super(nil)
end
def get_info(vm)
if vm.deploy_id
deploy_id = vm.deploy_id
else
deploy_id = vm['DEPLOY_ID']
end
if deploy_id and vm.vm_info[:dumpxml].nil?
vm.vm_info[:dumpxml] = `#{COMMANDS[:virsh]} dumpxml #{deploy_id} \
2>/dev/null`
vm.vm_info.each_key do |k|
vm.vm_info[k] = nil if vm.vm_info[k].to_s.strip.empty?
end
end
end
def get_tap(vm)
dumpxml = vm.vm_info[:dumpxml]
if dumpxml
dumpxml_root = REXML::Document.new(dumpxml).root
xpath = "devices/interface[@type='bridge']/"
xpath << "mac[@address='#{self[:mac]}']/../target"
tap = dumpxml_root.elements[xpath]
if tap
self[:tap] = tap.attributes['dev']
end
end
self
end
end
# A NIC using Xen. This class implements functions to get the physical interface
# that the NIC is using
class NicXen < Hash
def initialize
super(nil)
end
def get_info(vm)
if vm.deploy_id
deploy_id = vm.deploy_id
else
deploy_id = vm['DEPLOY_ID']
end
if deploy_id and (vm.vm_info[:domid].nil? or vm.vm_info[:networks].nil?)
vm.vm_info[:domid] =`#{COMMANDS[:xm]} domid #{deploy_id}`.strip
vm.vm_info[:networks] =`#{COMMANDS[:xm]} network-list #{deploy_id}`
vm.vm_info.each_key do |k|
vm.vm_info[k] = nil if vm.vm_info[k].to_s.strip.empty?
end
end
end
def get_tap(vm)
domid = vm.vm_info[:domid]
if domid
networks = vm.vm_info[:networks].split("\n")[1..-1]
networks.each do |net|
n = net.split
iface_id = n[0]
iface_mac = n[2]
if iface_mac == self[:mac]
self[:tap] = "vif#{domid}.#{iface_id}"
break
end
end
end
self
end
end
# A NIC using VMware. This class implements functions to get the physical interface
# that the NIC is using
class NicVMware < Hash
def initialize
super(nil)
end
def get_info(vm)
end
def get_tap(vm)
self
end
end

View File

@ -1,597 +0,0 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'IPNetmask'
################################################################################
# SecurityGroups and Rules
################################################################################
class CommandsError < StandardError; end
class Commands
def initialize
clear!
end
def add(cmd)
if cmd.instance_of?(String)
@commands << cmd
else
@commands.concat(cmd.to_a)
end
end
def method_missing(m, *args, &block)
if COMMANDS.keys.include?(m)
@commands << "#{COMMANDS[m]} #{args.join(' ')}"
else
super
end
end
def run!
out = ""
@commands.each{|c|
out << `#{c}`
if !$?.success?
clear!
raise CommandsError.new(c), "Command Error: #{c}"
end
}
clear!
out
end
def uniq!
@commands.uniq!
end
def clear!
@commands = []
end
def to_a
@commands
end
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
if !valid?
raise RuleError.new, "Invalid Rule: #{error_message}"
end
end
# Getters
def protocol
p = @rule[:protocol].downcase.to_sym rescue nil
if p == :ipsec
:esp
else
p
end
end
def rule_type
@rule[:rule_type].downcase.to_sym rescue nil
end
def range
@rule[:range]
end
def net
return nil if @rule[:ip].nil? || @rule[:size].nil?
r = Range.new(@rule[:ip], @rule[:size].to_i)
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 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
if !rule_type || ![:inbound, :outbound].include?(rule_type)
error_message << "Invalid rule_type: #{rule_type}"
valid = false
end
if range && !range.match(/^(?:(?:\d+|\d+:\d+),)*(?:\d+|\d+:\d+)$/)
error_message << "Invalid range: #{range}"
valid = false
end
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
if net && !valid_net?
error_message << "Invalid net: IP:'#{@rule[:ip]}' SIZE:'#{@rule[:size]}'"
valid = false
end
return [valid, error_message.join("\n")]
end
def valid_net?
@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 '<TYPE>' or '<TYPE>/<CODE>', where both
# '<TYPE>' and '<CODE>' are integers. This class has a helper method
# tgat expands '<TYPE>' into all the '<TYPE>/<CODE>' 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
def initialize(vm, nic, sg_id, rules)
@vm = vm
@nic = nic
@sg_id = sg_id
@rules = []
@invalid_rules = []
rules.each do |rule|
@rules << Rule.new(rule)
end if rules
end
end
################################################################################
# IPTables Implementation
################################################################################
class SecurityGroupIPTables < SecurityGroup
GLOBAL_CHAIN = "opennebula"
def initialize(vm, nic, sg_id, rules)
super
@commands = Commands.new
@vars = self.class.vars(@vm, @nic, @sg_id)
@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|
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_in
set = "#{@set_sg_in}-#{rule.protocol}-n"
dir = "src"
else
chain = @chain_out
set = "#{@set_sg_out}-#{rule.protocol}-n"
dir = "dst"
end
@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
when :net_portrange
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.range.split(",").each do |r|
r.gsub!(":","-")
net_range = "#{n},#{rule.protocol}:#{r}"
@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 ACCEPT"
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] == "YES"
commands.iptables"-A #{chain_out} -m mac ! --mac-source #{nic[:mac]} -j DROP"
end
# IP-spofing
if nic[:filter_ip_spoofing] == "YES"
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
################################################################################
# OpenNebula Firewall with Security Groups Based on IPTables (KVM and Xen)
################################################################################
class OpenNebulaSG < OpenNebulaNetwork
DRIVER = "sg"
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 get_security_group_rules
rules = {}
@vm.vm_root.elements.each('TEMPLATE/SECURITY_GROUP_RULE') do |r|
security_group_rule = {}
r.elements.each do |e|
key = e.name.downcase.to_sym
security_group_rule[key] = e.text
end
id = security_group_rule[:security_group_id]
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|
next if nic[:security_groups].nil? \
&& nic[:filter_mac_spoofing] != "YES" \
&& nic[:filter_ip_spoofing] != "YES"
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]
sg = SECURITY_GROUP_CLASS.new(@vm, nic, sg_id, rules)
begin
sg.process_rules
sg.run!
rescue Exception => e
unlock
deactivate
raise e
end
end
SECURITY_GROUP_CLASS.nic_post(@vm, nic)
end
unlock
end
def deactivate
lock
begin
@vm.nics.each do |nic|
SECURITY_GROUP_CLASS.nic_deactivate(@vm, nic)
end
rescue Exception => e
raise e
ensure
unlock
end
end
end

View File

@ -0,0 +1,198 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
module VNMMAD
############################################################################
# Filter network driver based on simple iptables rules
############################################################################
class OpenNebulaFirewall < VNMMAD::OpenNebulaNetwork
# Driver name
DRIVER = "fw"
# NICs filter. Select NICs with one or more of ICMP, WHITE_PORTS_* or
# BLACK_PORTS_*
XPATH_FILTER = "TEMPLATE/NIC[ICMP|WHITE_PORTS_TCP|WHITE_PORTS_UDP|" <<
"BLACK_PORTS_TCP|BLACK_PORTS_UDP]"
# Creates the driver object. It sets locking to prevent race conditions
# between concurrent deployments
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm,XPATH_FILTER,deploy_id,hypervisor)
@locking = true
end
# Function to activate the driver in the VM
#
def activate
lock
vm_id = @vm['ID']
process do |nic|
#:white_ports_tcp => iptables_range
#:white_ports_udp => iptables_range
#:black_ports_tcp => iptables_range
#:black_ports_udp => iptables_range
#:icmp => 'DROP' or 'NO'
nic_rules = Array.new
chain = "one-#{vm_id}-#{nic[:network_id]}"
tap = nic[:tap]
next if chain_exists?(chain)
if tap
#TCP
if range = nic[:white_ports_tcp]
nic_rules << filter_established(chain, :tcp, :accept)
nic_rules << filter_ports(chain, :tcp, range, :accept)
nic_rules << filter_protocol(chain, :tcp, :drop)
elsif range = nic[:black_ports_tcp]
nic_rules << filter_ports(chain, :tcp, range, :drop)
end
#UDP
if range = nic[:white_ports_udp]
nic_rules << filter_established(chain, :udp, :accept)
nic_rules << filter_ports(chain, :udp, range, :accept)
nic_rules << filter_protocol(chain, :udp, :drop)
elsif range = nic[:black_ports_udp]
nic_rules << filter_ports(chain, :udp, range, :drop)
end
#ICMP
if nic[:icmp]
if %w(no drop).include? nic[:icmp].downcase
nic_rules << filter_established(chain, :icmp, :accept)
nic_rules << filter_protocol(chain, :icmp, :drop)
end
end
process_chain(chain, tap, nic_rules)
end
end
unlock
end
def deactivate
lock
vm_id = @vm['ID']
process do |nic|
chain = "one-#{vm_id}-#{nic[:network_id]}"
iptables_out = `#{COMMANDS[:iptables]} -n -v --line-numbers -L FORWARD`
if m = iptables_out.match(/.*#{chain}.*/)
rule_num = m[0].split(/\s+/)[0]
purge_chain(chain, rule_num)
end
end
unlock
end
########################################################################
# Methods to deal with iptables rules
########################################################################
private:
def purge_chain(chain, rule_num)
rules = Array.new
rules << rule("-D FORWARD #{rule_num}")
rules << rule("-F #{chain}")
rules << rule("-X #{chain}")
run_rules rules
end
def process_chain(chain, tap, nic_rules)
rules = Array.new
if !nic_rules.empty?
# new chain
rules << new_chain(chain)
# move tap traffic to chain
rules << tap_to_chain(tap, chain)
rules << nic_rules
end
run_rules rules
end
def filter_established(chain, protocol, policy)
policy = policy.to_s.upcase
rule "-A #{chain} -p #{protocol} -m state --state ESTABLISHED -j #{policy}"
end
def run_rules(rules)
rules.flatten.each do |rule|
OpenNebula.exec_and_log(rule)
end
end
def range?(range)
range.match(/^(?:(?:\d+|\d+:\d+),)*(?:\d+|\d+:\d+)$/)
end
def filter_protocol(chain, protocol, policy)
policy = policy.to_s.upcase
rule "-A #{chain} -p #{protocol} -j #{policy}"
end
def filter_ports(chain, protocol, range, policy)
policy = policy.to_s.upcase
range.gsub!(/\s+/,"")
if range? range
rule "-A #{chain} -p #{protocol} -m multiport --dports #{range} -j #{policy}"
end
end
def tap_to_chain(tap, chain)
iptables_out = `#{COMMANDS[:iptables]} -n -v --line-numbers -L FORWARD`
# Insert the rule on top of the 'opennebula' chain if it exists, so it
# doesn't conflict with the security groups driver
index = nil
iptables_out.lines.each do |line|
fields = line.split
if fields.include?("opennebula") && fields.include?("--physdev-is-bridged")
index = fields[0]
break
end
end
if index
rule "-I FORWARD #{index} -m physdev --physdev-out #{tap} -j #{chain}"
else
rule "-A FORWARD -m physdev --physdev-out #{tap} -j #{chain}"
end
end
def new_chain(chain)
rule "-N #{chain}"
end
def chain_exists?(chain)
iptables_nl =`#{COMMANDS[:iptables]} -nL`
chains = iptables_nl.scan(/(one-.*?) .*references/).flatten
chains.include? chain
end
def rule(rule)
"#{COMMANDS[:iptables]} #{rule}"
end
end
end

View File

@ -0,0 +1,107 @@
# -------------------------------------------------------------------------- #
# Copyright 2002-2014, OpenNebula Project (OpenNebula.org), C12G Labs #
# #
# Licensed under the Apache License, Version 2.0 (the "License"); you may #
# not use this file except in compliance with the License. You may obtain #
# a copy of the License at #
# #
# http://www.apache.org/licenses/LICENSE-2.0 #
# #
# Unless required by applicable law or agreed to in writing, software #
# distributed under the License is distributed on an "AS IS" BASIS, #
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. #
# See the License for the specific language governing permissions and #
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'opennebula_network'
module VNMMAD
############################################################################
# OpenNebula Firewall with Security Groups Based on IPTables (KVM and Xen)
############################################################################
class OpenNebulaSG < VNMMAD::OpenNebulaNetwork
DRIVER = "sg"
XPATH_FILTER = "TEMPLATE/NIC"
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm, XPATH_FILTER, deploy_id, hypervisor)
@locking = true
@commands = Commands.new
rules = {}
@vm.vm_root.elements.each('TEMPLATE/SECURITY_GROUP_RULE') do |r|
security_group_rule = {}
r.elements.each do |e|
key = e.name.downcase.to_sym
security_group_rule[key] = e.text
end
id = security_group_rule[:security_group_id]
rules[id] = [] if rules[id].nil?
rules[id] << security_group_rule
end
@security_group_rules = rules
end
def activate
deactivate
lock
# Global Bootstrap
VNMMAD::SGIPTables.global_bootstrap
# Process the rules
@vm.nics.each do |nic|
next if nic[:security_groups].nil? \
&& nic[:filter_mac_spoofing] != "YES" \
&& nic[:filter_ip_spoofing] != "YES"
VNMMAD::SGIPTables.nic_pre(@vm, nic)
sg_ids = nic[:security_groups].split(",")
sg_ids.each do |sg_id|
rules = @security_group_rules[sg_id]
sg = VNMMAD::SGIPTables::SecurityGroupIPTables.new(@vm, nic,
sg_id, rules)
begin
sg.process_rules
sg.run!
rescue Exception => e
unlock
deactivate
raise e
end
end
VNMMAD::SGIPTables.nic_post(@vm, nic)
end
unlock
end
def deactivate
lock
begin
@vm.nics.each do |nic|
VNMMAD::SGIPTables.nic_deactivate(@vm, nic)
end
rescue Exception => e
raise e
ensure
unlock
end
end
end
end

View File

@ -23,11 +23,11 @@ require 'yaml'
require 'one_firewall'
require 'one_sg'
require 'lib/vm'
require 'lib/nic'
require 'lib/address'
require 'lib/security_groups'
require 'lib/security_groups_iptables'
require 'vm'
require 'nic'
require 'address'
require 'security_groups'
require 'security_groups_iptables'
require 'scripts_common'