1
0
mirror of https://github.com/OpenNebula/one.git synced 2025-03-21 14:50:08 +03:00

Merge branch 'feature-3175b'

This commit is contained in:
Ruben S. Montero 2014-12-29 15:30:58 +01:00
commit e061f11f69
29 changed files with 1752 additions and 1396 deletions

View File

@ -158,6 +158,15 @@ namespace one_util
* @return
*/
std::string float_to_str(const float &num);
/**
* Checks if a strings matches a regular expression
*
* @param pattern PCRE extended pattern
* @param subject the string to test
* @return 0 on match, another value otherwise
*/
int regex_match(const char *pattern, const char *subject);
};
#endif /* _NEBULA_UTIL_H_ */

View File

@ -932,17 +932,22 @@ 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/vnm_driver.rb \
src/vnm_mad/remotes/lib/vnmmad.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/fw_driver.rb \
src/vnm_mad/remotes/lib/sg_driver.rb \
src/vnm_mad/remotes/lib/address.rb \
src/vnm_mad/remotes/lib/command.rb \
src/vnm_mad/remotes/lib/vm.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 \
src/vnm_mad/remotes/802.1Q/pre \
src/vnm_mad/remotes/802.1Q/HostManaged.rb"
src/vnm_mad/remotes/802.1Q/vlan_driver.rb"
NETWORK_DUMMY_FILES="src/vnm_mad/remotes/dummy/clean \
src/vnm_mad/remotes/dummy/post \

View File

@ -28,6 +28,8 @@
#include <iomanip>
#include <algorithm>
#include <math.h>
#include <sys/types.h>
#include <regex.h>
using namespace std;
@ -260,3 +262,24 @@ string one_util::float_to_str(const float &num)
return oss.str();
}
/* -------------------------------------------------------------------------- */
/* -------------------------------------------------------------------------- */
int one_util::regex_match(const char *pattern, const char *subject)
{
int rc;
regex_t re;
rc = regcomp(&re, pattern, REG_EXTENDED|REG_NOSUB);
if (rc != 0)
{
return(rc);
}
rc = regexec(&re, subject, 0, 0, 0);
regfree(&re);
return rc;
}

View File

@ -349,6 +349,16 @@ bool SecurityGroup::isValidRule(const VectorAttribute * rule, string& error) con
return false;
}
if (!value.empty())
{
const char *range_pattern = "^(([[:digit:]]+|[[:digit:]]+:[[:digit:]]+),)*([[:digit:]]+|[[:digit:]]+:[[:digit:]]+)$";
if (one_util::regex_match(range_pattern, value.c_str()) != 0)
{
error = "Invalid RANGE specification.";
return false;
}
}
value = rule->vector_value("ICMP_TYPE");
if (!value.empty())

View File

@ -19,7 +19,7 @@
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "..")
require 'HostManaged'
require 'vlan_driver'
hm = OpenNebulaHM.from_base64(ARGV[0])
hm = VLANDriver.from_base64(ARGV[0])
exit hm.activate

View File

@ -14,15 +14,26 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'OpenNebulaNetwork'
require 'vnmmad'
class OpenNebulaHM < OpenNebulaNetwork
DRIVER = "802.1Q"
################################################################################
# This driver tag VM traffic with a VLAN_ID using 802.1Q protocol. Features:
# - Creates a bridge and bind phisycal device if not present
# - Creates a tagged interface for the VM dev.vlan_id
#
# Once activated the VM will be attached to this bridge
################################################################################
class VLANDriver < VNMMAD::VNMDriver
# DRIVER name and XPATH for relevant NICs
DRIVER = "802.1Q"
XPATH_FILTER = "TEMPLATE/NIC[VLAN='YES']"
############################################################################
# Creatges the driver device operations are not locked
############################################################################
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm,XPATH_FILTER,deploy_id,hypervisor)
super(vm, XPATH_FILTER, deploy_id, hypervisor)
@locking = false
lock
@ -30,10 +41,14 @@ class OpenNebulaHM < OpenNebulaNetwork
unlock
end
############################################################################
# Activate the driver and creates bridges and tags devices as needed.
############################################################################
def activate
lock
vm_id = @vm['ID']
process do |nic|
bridge = nic[:bridge]
dev = nic[:phydev]
@ -66,23 +81,29 @@ class OpenNebulaHM < OpenNebulaNetwork
return 0
end
############################################################################
# Private interface, methods to manage bridges and VLAN tags through the
# brctl and ip commands
############################################################################
private
def bridge_exists?(bridge)
@bridges.keys.include? bridge
end
def create_bridge(bridge)
OpenNebula.exec_and_log("#{COMMANDS[:brctl]} addbr #{bridge}")
OpenNebula.exec_and_log("#{command(:brctl)} addbr #{bridge}")
@bridges[bridge] = Array.new
end
def device_exists?(dev, vlan=nil)
dev = "#{dev}.#{vlan}" if vlan
`#{COMMANDS[:ip]} link show #{dev}`
`#{command(:ip)} link show #{dev}`
$?.exitstatus == 0
end
def create_dev_vlan(dev, vlan)
cmd = "#{COMMANDS[:ip]} link add link #{dev}"
cmd = "#{command(:ip)} link add link #{dev}"
cmd << " name #{dev}.#{vlan} type vlan id #{vlan}"
OpenNebula.exec_and_log(cmd)
@ -90,18 +111,20 @@ class OpenNebulaHM < OpenNebulaNetwork
def attached_bridge_dev?(bridge, dev, vlan=nil)
return false if !bridge_exists? bridge
dev = "#{dev}.#{vlan}" if vlan
@bridges[bridge].include? dev
end
def attach_brigde_dev(bridge, dev, vlan=nil)
dev = "#{dev}.#{vlan}" if vlan
OpenNebula.exec_and_log("#{COMMANDS[:brctl]} addif #{bridge} #{dev}")
OpenNebula.exec_and_log("#{command(:brctl)} addif #{bridge} #{dev}")
@bridges[bridge] << dev
end
def ifup(dev, vlan=nil)
dev = "#{dev}.#{vlan}" if vlan
OpenNebula.exec_and_log("#{COMMANDS[:ip]} link set #{dev} up")
OpenNebula.exec_and_log("#{command(:ip)} link set #{dev} up")
end
end

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

@ -14,9 +14,9 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'OpenNebulaNetwork'
require 'vnmmad'
class EbtablesVLAN < OpenNebulaNetwork
class EbtablesVLAN < VNMMAD::VNMDriver
DRIVER = "ebtables"
XPATH_FILTER = "TEMPLATE/NIC[VLAN='YES']"

View File

@ -19,7 +19,6 @@
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "..")
require 'OpenNebulaNetwork'
require 'Ebtables'
template64 = ARGV[0]
@ -27,5 +26,5 @@ template64 = ARGV[0]
onevlan = EbtablesVLAN.from_base64(template64)
onevlan.deactivate
filter_driver = OpenNebulaNetwork.filter_driver(template64)
filter_driver = VNMMAD::VNMDriver.filter_driver(template64)
filter_driver.deactivate

View File

@ -19,7 +19,6 @@
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "..")
require 'OpenNebulaNetwork'
require 'Ebtables'
template64 = ARGV[0]
@ -29,7 +28,7 @@ onevlan = EbtablesVLAN.from_base64(template64, deploy_id)
onevlan.activate
begin
filter_driver = OpenNebulaNetwork.filter_driver(template64, deploy_id)
filter_driver = VNMMAD::VNMDriver.filter_driver(template64, deploy_id)
filter_driver.activate
rescue Exception => e
OpenNebula.log_error(e.message)

View File

@ -19,12 +19,12 @@
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "..")
require 'OpenNebulaNetwork'
require 'vnmmad'
template64 = ARGV[0]
begin
filter_driver = OpenNebulaNetwork.filter_driver(template64)
filter_driver = VNMMAD::VNMDriver.filter_driver(template64)
filter_driver.deactivate
rescue Exception => e
OpenNebula.log_error(e.message)

View File

@ -19,13 +19,13 @@
$: << File.dirname(__FILE__)
$: << File.join(File.dirname(__FILE__), "..")
require 'OpenNebulaNetwork'
require 'vnmmad'
template64 = ARGV[0]
deploy_id = ARGV[1]
begin
filter_driver = OpenNebulaNetwork.filter_driver(template64, deploy_id)
filter_driver = VNMMAD::VNMDriver.filter_driver(template64, deploy_id)
filter_driver.activate
rescue Exception => e
OpenNebula.log_error(e.message)

View File

@ -0,0 +1,77 @@
# -------------------------------------------------------------------------- #
# 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
module VNMNetwork
# This methods translates an address range to a set of IPv4 networks
# in CIDR notation
# @param ip_start [String] First IP of the range in dot notation
# @param size [Fixnum] The number of IPs in the range
#
# @return [Array<String>] The networks in CIDR
def self.to_nets(ip_start, size)
nets = Array.new
ip_i = IPv4.to_i(ip_start)
# Find the largest address block (look for the first 1-bit)
lblock = 0
lblock += 1 while (ip_i[lblock] == 0 && lblock < 32 )
# Allocate whole blocks till the size fits
while ( size >= 2**lblock )
nets << "#{IPv4.to_s(ip_i)}/#{32-lblock}"
ip_i += 2**lblock
size -= 2**lblock
lblock += 1 while (ip_i[lblock] == 0 && lblock < 32 )
end
# Fit remaining address blocks
32.downto(0) { |i|
next if size[i] == 0
nets << "#{IPv4.to_s(ip_i)}/#{32-i}"
ip_i += 2**i
}
return nets
end
# This implementation module includes IPv4 management functions
# It MUST NOT be used in other VNMAD classes
module IPv4
# Returns the binary equivalent of a IP address
# @param ip [String] IP in dot notation
# @return [Fixnum] IP as an integer
def self.to_i(ip)
ip.split(".").inject(0) {|t,e| (t << 8) + e.to_i }
end
# Returns the string equivalent of a IP address
# @param ip [Fixnum] IP as an integer
# @return [String] IP in dot notation
def self.to_s(ip)
ip = 3.downto(0).collect {|s| (ip >> 8*s) & 0xff }.join('.')
end
end
end
end

View File

@ -0,0 +1,86 @@
# -------------------------------------------------------------------------- #
# 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
module VNMNetwork
# This module include implementation specific functions. It MUST not be
# be used in other VNMAD classes.
module Configuration
# Return the command to talk to the Xen hypervisor xm or xl for
# Xen 3 and 4
def self.get_xen_command
if system("ps axuww | grep -v grep | grep '\\bxen\\b'")
"sudo xm"
else
"sudo xl"
end
end
end
# Command configuration for common network commands. This CAN be adjust
# to local installations. Any modification requires to sync the hosts with
# onehost sync command.
COMMANDS = {
:ebtables => "sudo ebtables",
:iptables => "sudo iptables",
:brctl => "sudo brctl",
:ip => "sudo ip",
:virsh => "virsh -c qemu:///system",
:xm => Configuration::get_xen_command,
:ovs_vsctl=> "sudo ovs-vsctl",
:ovs_ofctl=> "sudo ovs-ofctl",
:lsmod => "lsmod",
:ipset => "sudo ipset"
}
# Represents an Array of commands to be executed by the networking drivers
# The commands
class Commands < Array
# Adds a new command to the command array
# @param cmd [String] the command, it can be a key defined in COMMANDS
# @para args[Array<String>] Arguments for the command
def add (cmd, *args)
if COMMANDS.keys.include?(cmd.to_sym)
cmd_str = "#{COMMANDS[cmd.to_sym]} #{args.join(' ')}"
else
cmd_str = "#{cmd} #{args.join(' ')}"
end
self << cmd_str
end
# Executes the commands array
# @return [String] the output of the commands
def run!
out = ""
self.each{ |c|
out << `#{c}`
raise StandardError, "Command Error: #{c}" if !$?.success?
}
clear
return out
end
end
end
end

View File

@ -0,0 +1,199 @@
# -------------------------------------------------------------------------- #
# 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 FWDriver < VNMDriver
# 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
# Method to clean iptables chains
def deactivate
lock
vm_id = @vm['ID']
process do |nic|
chain = "one-#{vm_id}-#{nic[:network_id]}"
iptables_out = `#{command(: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 = `#{command(: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 =`#{command(:iptables)} -nL`
chains = iptables_nl.scan(/(one-.*?) .*references/).flatten
chains.include? chain
end
def rule(rule)
"#{command(:iptables)} #{rule}"
end
end
end

View File

@ -0,0 +1,154 @@
# -------------------------------------------------------------------------- #
# 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
module VNMNetwork
# This class represents the NICS of a VM, it provides a factory method
# to create VMs of the given hyprtvisor
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
############################################################################
# Hypervisor specific implementation of network interfaces. Each class
# implements the following interface:
# - get_info to populste the VM.vm_info Hash
# - get_tap to set the [:tap] attribute with the associated NIC
############################################################################
# A NIC using KVM. This class implements functions to get the physical
# interface that the NIC is using, based on the MAC address
class NicKVM < Hash
def initialize
super(nil)
end
# Get the VM information with virsh dumpxml
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] = `#{VNMNetwork::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
# Look for the tap in
# devices/interface[@type='bridge']/mac[@address='<mac>']/../target"
def get_tap(vm)
dumpxml = vm.vm_info[:dumpxml]
if dumpxml
dumpxml_root = REXML::Document.new(dumpxml).root
xpath = "devices/interface[@type='bridge']/" \
"mac[@address='#{self[:mac]}']/../target"
tap = dumpxml_root.elements[xpath]
self[:tap] = tap.attributes['dev'] if tap
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] =`#{VNMNetwork::COMMANDS[:xm]} domid #{deploy_id}`.strip
vm.vm_info[:networks] =`#{VNMNetwork::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
end
end

View File

@ -0,0 +1,222 @@
# -------------------------------------------------------------------------- #
# 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
# This module includes provides the abstractions to implement SecurityGroups
module VNMNetwork
############################################################################
# Rule supports 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
############################################################################
class Rule
# Rule type.
TYPES = [
:protocol, # Type 1: block the whole protocol
:portrange, # Type 2a: block a port range within a protocol
:icmp_type, # Type 2b: block selected icmp types
:net, # Type 3: block a whole protocol for a network
:net_portrange, # Type 4a: block a port range from a network
:net_icmp_type # Type 4b: block selected icmp types from a network
]
# Initialize a new rule.
def initialize(rule)
@rule = rule
@protocol = @rule[:protocol].downcase.to_sym
@protocol = :esp if @protocol == :ipsec
@rule_type = @rule[:rule_type].downcase.to_sym
@icmp_type = @rule[:icmp_type]
@range = @rule[:range]
@ip = @rule[:ip]
@size = @rule[:size]
@type = set_type
end
# Process the rule and generates the associated commands of the rule
# @param [Commands] cmd to add the rule commands to
# @param [Hash] vars iptables attributes for the rule
def process(cmds, vars)
case @type
when :protocol
process_protocol(cmds, vars)
when :portrange
process_portrange(cmds, vars)
when :icmp_type
process_icmp_type(cmds, vars)
when :net
process_net(cmds, vars)
when :net_portrange
process_net_portrange(cmds, vars)
when :net_icmp_type
process_net_icmp_type(cmds, vars)
end
end
# Return the network blocks associated to the rule
# @return [Array<String>] each network block in CIDR.
def net
return [] if @ip.nil? || @size.nil?
VNMNetwork::to_nets(@ip, @size.to_i)
end
# Expand the ICMP type with associated codes if any
# @return [Array<String>] expanded ICMP types to include all codes
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
private
# ICMP Codes for each ICMP type
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]
}
# Depending on the combination of the rule attributes derive the
# rule type:
#
# @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 set_type
if @ip.nil? && @size.nil?
return :icmp_type if !@icmp_type.nil?
return :portrange if !@range.nil?
return :protocol
else
return :net_icmp_type if !@icmp_type.nil?
return :net_portrange if !@range.nil?
return :net
end
end
########################################################################
# Dummy process methods for each rule type. These MUST be overriden
# in derived classes
# @param cmds [Commands] commands to implement the rule
# @param vars [Hash] with specific rule implementation variables
########################################################################
def process_protocol(cmds, vars)
end
def process_portrange(cmds, vars)
end
def process_icmp_type(cmds, vars)
end
def process_net(cmds, vars)
end
def process_net_portrange(cmds, vars)
end
def process_net_icmp_type(cmds, vars)
end
end
############################################################################
# Base class for security groups. This class SHOULD NOT be used directly
############################################################################
class SecurityGroup
# Creates a new security group
# @param vm [VNMMAD::VM] a VM object
# @param nic [VNMMAD::NIC] the network interface
# @param sg_id [Fixnum] the security group ID
# @param rules [Array<Hash>] to be applied to the NIC
def initialize(vm, nic, sg_id, rules)
@vm = vm
@nic = nic
@sg_id = sg_id
@rules = []
@vars = {}
@commands = VNMNetwork::Commands.new
rules.each do |rule|
@rules << new_rule(rule)
end if rules
end
# Default factory method for the SecurityGroup class. It MUST be
# overriden in derived classes
def new_rule(rule)
Rule.new(rule)
end
# Generates the iptables/ipset commands to implement this security group
def process_rules
@rules.each do |rule|
rule.process(@commands, @vars)
end
@commands.uniq!
end
# Execute the implementation commands, process_rules MUST be called
# before this method
def run!
@commands.run!
end
end
end
end

View File

@ -0,0 +1,341 @@
# -------------------------------------------------------------------------- #
# 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
# This module implements the SecurityGroup abstraction on top of iptables
module SGIPTables
############################################################################
# A Rule implemented with the iptables/ipset Linux kernel facilities
############################################################################
class RuleIPTables < VNMNetwork::Rule
########################################################################
# Implementation of each rule type
########################################################################
private
# Implements the :protocol rule. Example:
# iptables -A one-3-0-i -p tcp -j RETURN
def process_protocol(cmds, vars)
chain = @rule_type == :inbound ? vars[:chain_in] : vars[:chain_out]
cmds.add :iptables, "-A #{chain} -p #{@protocol} -j RETURN"
end
# Implements the :portrange rule. Example:
# iptables -A one-3-0-o -p udp -m multiport --dports 80,22 -j RETURN
def process_portrange(cmds, vars)
chain = @rule_type == :inbound ? vars[:chain_in] : vars[:chain_out]
cmds.add :iptables, "-A #{chain} -p #{@protocol} -m multiport" \
" --dports #{@range} -j RETURN"
end
# Implements the :icmp_type rule. Example:
# iptables -A one-3-0-o -p icmp --icmp-type 8 -j RETURN
def process_icmp_type(cmds, vars)
chain = @rule_type == :inbound ? vars[:chain_in] : vars[:chain_out]
cmds.add :iptables, "-A #{chain} -p icmp --icmp-type #{@icmp_type}" \
" -j RETURN"
end
# Implements the :net rule. Example:
# ipset create one-3-0-1-i-tcp-n hash:net
# iptables -A one-3-0-i -p tcp -m set --match-set one-3-0-1-i src -j RETURN
# ipset add -exist one-3-0-1-i-tcp-n 10.0.0.0/24
def process_net(cmds, vars)
if @rule_type == :inbound
chain = vars[:chain_in]
set = "#{vars[:set_sg_in]}-#{@protocol}-n"
dir = "src"
else
chain = vars[:chain_out]
set = "#{vars[:set_sg_out]}-#{@protocol}-n"
dir = "dst"
end
cmds.add :ipset, "create #{set} hash:net"
cmds.add :iptables, "-A #{chain} -p #{@protocol} -m set" \
" --match-set #{set} #{dir} -j RETURN"
net.each do |n|
cmds.add :ipset, "add -exist #{set} #{n}"
end
end
# Implements the :net_portrange rule. Example:
# ipset create one-3-0-1-i-nr hash:net,port
# iptables -A one-3-0-i -m set --match-set one-3-0-1-i-nr src,dst -j RETURN
# ipset add -exist one-3-0-1-i-nr 10.0.0.0/24,tcp:80
def process_net_portrange(cmds, vars)
if @rule_type == :inbound
chain = vars[:chain_in]
set = "#{vars[:set_sg_in]}-nr"
dir = "src,dst"
else
chain = vars[:chain_out]
set = "#{vars[:set_sg_out]}-nr"
dir = "dst,dst"
end
cmds.add :ipset, "create #{set} hash:net,port"
cmds.add :iptables, "-A #{chain} -m set --match-set" \
" #{set} #{dir} -j RETURN"
net.each do |n|
@range.split(",").each do |r|
r.gsub!(":","-")
net_range = "#{n},#{@protocol}:#{r}"
cmds.add :ipset, "add -exist #{set} #{net_range}"
end
end
end
# Implements the :net_icmp_type rule. Example:
# ipset create one-3-0-1-i-ni hash:net,port
# iptables -A one-3-0-i -m set --match-set one-3-0-1-i-nr src,dst -j RETURN
# ipset add -exist one-3-0-1-i-ni 10.0.0.0/24,icmp:8/0
def process_net_icmp_type(cmds, vars)
if @rule_type == :inbound
chain = vars[:chain_in]
set = "#{vars[:set_sg_in]}-ni"
dir = "src,dst"
else
chain = vars[:chain_out]
set = "#{vars[:set_sg_out]}-ni"
dir = "dst,dst"
end
cmds.add :ipset, "create #{set} hash:net,port"
cmds.add :iptables, "-A #{chain} -m set --match-set #{set} #{dir} -j RETURN"
net.each do |n|
icmp_type_expand.each do |type_code|
cmds.add :ipset, "add -exist #{set} #{n},icmp:#{type_code}"
end
end
end
end
############################################################################
# This class represents a SecurityGroup implemented with iptables/ipset
# Kernel facilities.
############################################################################
class SecurityGroupIPTables < VNMNetwork::SecurityGroup
def initialize(vm, nic, sg_id, rules)
super
@vars = SGIPTables.vars(@vm, @nic, @sg_id)
end
def new_rule(rule)
RuleIPTables.new(rule)
end
end
############################################################################
# Methods to configure the hypervisor iptables rules. All the rules are
# added to the GLOBAL_CHAIN chain. By default this chain is "opennebula"
############################################################################
GLOBAL_CHAIN = "opennebula"
# Get information from the current iptables rules and chains
# @return [Hash] with the following keys:
# - :iptables_forwards
# - :iptables_s
# - :ipset_list
def self.info
commands = VNMNetwork::Commands.new
commands.add :iptables, "-S"
iptables_s = commands.run!
iptables_forwards = ""
if iptables_s.match(/^-N #{GLOBAL_CHAIN}$/)
commands.add :iptables, "-L #{GLOBAL_CHAIN} --line-numbers"
iptables_forwards = commands.run!
end
commands.add :ipset, "list -name"
ipset_list = commands.run!
{
:iptables_forwards => iptables_forwards,
:iptables_s => iptables_s,
:ipset_list => ipset_list
}
end
# Bootstrap the OpenNebula chains and rules. This method:
# 1.- Creates the GLOBAL_CHAIN chain
# 2.- Forwards the bridge traffic to the GLOBAL_CHAIN
# 3.- By default ACCEPT all traffic
def self.global_bootstrap
info = SGIPTables.info
return if info[:iptables_s].split("\n").include? "-N #{GLOBAL_CHAIN}"
commands = VNMNetwork::Commands.new
commands.add :iptables, "-N #{GLOBAL_CHAIN}"
commands.add :iptables, "-A FORWARD -m physdev --physdev-is-bridged -j #{GLOBAL_CHAIN}"
commands.add :iptables, "-A #{GLOBAL_CHAIN} -j ACCEPT"
commands.run!
end
# Returns the base chain and ipset names for the VM
# @param vm [VM] the virtual machine
# @param nic [Nic] of the VM
# @param sg_id [Fixnum] ID of the SecurityGroup if any
#
# @return [Hash] with the :chain, :chain_in, :chain_out chain names, and
# :set_sg_in and :set_seg_out ipset names.
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
# Bootstrap NIC rules for the interface. It creates the :chain_in and
# :chain_out and sets up FORWARD rules to these chains for inbound and
# outbound traffic.
#
# This method also sets mac_spoofing, and ip_spoofing rules
#
# Example, for VM 3 and NIC 0
# iptables -N one-3-0-i
# iptables -N one-3-0-o
# iptables -I opennebula -m physdev --physdev-out vnet0 --physdev-is-bridged -j one-3-0-i"
# iptables -I opennebula -m physdev --physdev-in vnet0 --physdev-is-bridged -j one-3-0-o"
# iptables -A one-3-0-i -m state --state ESTABLISHED,RELATED -j ACCEPT
# iptables -A one-3-0-o -m state --state ESTABLISHED,RELATED -j ACCEPT
#
# Mac spoofing (no output traffic from a different MAC)
# iptables -A one-3-0-o -m mac ! --mac-source 02:00:00:00:00:01 -j DROP
#
# IP spoofing
# iptables -A one-3-0-o ! --source 10.0.0.1 -j DROP
def self.nic_pre(vm, nic)
commands = VNMNetwork::Commands.new
vars = SGIPTables.vars(vm, nic)
chain = vars[:chain]
chain_in = vars[:chain_in]
chain_out = vars[:chain_out]
# create chains
commands.add :iptables, "-N #{chain_in}" # inbound
commands.add :iptables, "-N #{chain_out}" # outbound
# Send traffic to the NIC chains
commands.add :iptables, "-I #{GLOBAL_CHAIN} -m physdev --physdev-out #{nic[:tap]} --physdev-is-bridged -j #{chain_in}"
commands.add :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.add :iptables, "-A #{chain_out} -m mac ! --mac-source #{nic[:mac]} -j DROP"
end
# IP-spofing
if nic[:filter_ip_spoofing] == "YES"
commands.add :iptables, "-A #{chain_out} ! --source #{nic[:ip]} -j DROP"
end
# Related, Established
commands.add :iptables, "-A #{chain_in} -m state --state ESTABLISHED,RELATED -j ACCEPT"
commands.add :iptables, "-A #{chain_out} -m state --state ESTABLISHED,RELATED -j ACCEPT"
commands.run!
end
# Sets the default policy to DROP for the NIC rules. Example
# iptables -A one-3-0-i -j DROP
# iptables -A one-3-0-o -j DROP
def self.nic_post(vm, nic)
vars = SGIPTables.vars(vm, nic)
chain_in = vars[:chain_in]
chain_out = vars[:chain_out]
commands = VNMNetwork::Commands.new
commands.add :iptables, "-A #{chain_in} -j DROP"
commands.add :iptables, "-A #{chain_out} -j DROP"
commands.run!
end
# Removes all the rules associated to a VM and NIC
def self.nic_deactivate(vm, nic)
vars = SGIPTables.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 = VNMNetwork::Commands.new
iptables_forwards.lines.reverse_each do |line|
fields = line.split
if [chain_in, chain_out].include?(fields[1])
n = fields[0]
commands.add :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.add :iptables, "-F #{c}" }
remove_chains.each {|c| commands.add :iptables, "-X #{c}" }
ipset_list.lines.each do |line|
if line.match(/^#{chain}/)
set = line.strip
commands.add :ipset, "destroy #{set}"
end
end
commands.run!
end
end
end

View File

@ -0,0 +1,105 @@
# -------------------------------------------------------------------------- #
# 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
############################################################################
# OpenNebula Firewall with Security Groups Based on IPTables (KVM and Xen)
############################################################################
class SGDriver < VNMDriver
DRIVER = "sg"
XPATH_FILTER = "TEMPLATE/NIC"
# Creates a new SG driver and scans SG Rules
def initialize(vm, deploy_id = nil, hypervisor = nil)
super(vm, XPATH_FILTER, deploy_id, hypervisor)
@locking = true
@commands = VNMNetwork::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
# Activate the rules, bootstrap iptables chains and set filter rules for
# each VM NIC
def activate
deactivate
lock
# Global Bootstrap
SGIPTables.global_bootstrap
# Process the rules
@vm.nics.each do |nic|
next if nic[:security_groups].nil?
SGIPTables.nic_pre(@vm, nic)
sg_ids = nic[:security_groups].split(",")
sg_ids.each do |sg_id|
rules = @security_group_rules[sg_id]
sg = SGIPTables::SecurityGroupIPTables.new(@vm, nic, sg_id,
rules)
begin
sg.process_rules
sg.run!
rescue Exception => e
unlock
deactivate
raise e
end
end
SGIPTables.nic_post(@vm, nic)
end
unlock
end
# Clean iptables rules and chains
def deactivate
lock
begin
@vm.nics.each do |nic|
SGIPTables.nic_deactivate(@vm, nic)
end
rescue Exception => e
raise e
ensure
unlock
end
end
end
end

View File

@ -0,0 +1,110 @@
# -------------------------------------------------------------------------- #
# 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
module VNMNetwork
############################################################################
# This class represents the VM abstraction. It provides basic methods
# to interact with its network interfaces.
############################################################################
class VM
attr_accessor :nics, :vm_info, :deploy_id, :vm_root
# Creates a new VM object, and bootstrap the NICs array
# @param vm_root [REXML] XML document representing the VM
# @param xpath_filer [String] to get the VM NICs
# @param deploy_id [String] refers to the VM in the hypervisor
# @param hypervisor [String]
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 = VNMNetwork::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
# Iterator on each NIC of the VM
def each_nic(block)
if @nics != nil
@nics.each do |the_nic|
block.call(the_nic)
end
end
end
# Access an XML Element of the VM
# @param element [String] element name
# @return [String] valule of the element or nil if not found
def [](element)
if @vm_root
val = @vm_root.elements[element]
return val.text if !val.nil? && val.text
end
nil
end
private
# Method to build the associated Hash from a NIC
# @param nic_element [REXML] for the NIC
# @param nic [Nic] class representation
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
end
end
end

View File

@ -0,0 +1,158 @@
# -------------------------------------------------------------------------- #
# 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. #
#--------------------------------------------------------------------------- #
################################################################################
# The VNMMAD module provides the basic abstraction to implement custom
# virtual network drivers. The VNMAD module includes:
# - VNMNetwork with base classes and main functionality to manage Virtual Nets
# - SGIPTables a module with a SG implementation based in iptables/ipset
################################################################################
module VNMMAD
############################################################################
# Base driver class to implement a Network driver. It relays on two filter
# drivers FirewallDriver and SGDriver.
############################################################################
class VNMDriver
attr_reader :hypervisor, :vm
# Creates new driver using:
# @param vm_tpl [String] XML String from oned
# @param xpath_filter [String] to get relevant NICs for the driver
# @param deploy_id [String]
# @param hypervisor [String]
def initialize(vm_tpl, xpath_filter, deploy_id = nil, hypervisor = nil)
@locking = false
if !hypervisor
@hypervisor = detect_hypervisor
else
@hypervisor = hypervisor
end
@vm = VNMNetwork::VM.new(REXML::Document.new(vm_tpl).root,
xpath_filter, deploy_id, @hypervisor)
end
# Creates a new VNDriver using:
# @param vm_64 [String] Base64 encoded XML String from oned
# @param deploy_id [String]
# @param hypervisor [String]
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
# Locking function to serialized driver operations if needed. Similar
# to flock. File is created as /tmp/onevnm-<driver>-lock
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
# Unlock driver execution mutex
def unlock
if @locking
@locking_file.close
end
end
# Executes the given block on each NIC
def process(&block)
@vm.each_nic(block)
end
# Return a string for the hypervisor
# @return [String] "kvm", "xen" or nil
def detect_hypervisor
lsmod = `#{VNMNetwork::COMMANDS[:lsmod]}`
xen_file = "/proc/xen/capabilities"
if File.exists?(xen_file)
"xen"
elsif lsmod.match(/kvm/)
"kvm"
else
nil
end
end
# Get hypervisor bridges
# @return [Hash<String>] with the bridge names
def get_interfaces
bridges = Hash.new
brctl_exit =`#{VNMNetwork::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
# 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[FWDriver::XPATH_FILTER].nil?
end
# Returns a filter object based on the contents of the template
#
# @return FWDriver or SGDriver 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)
FWDriver.new(vm_xml, deploy_id, hypervisor)
else
SGDriver.new(vm_xml, deploy_id, hypervisor)
end
end
# Returns the associated command including sudo and other configuration
# attributes
def command(cmd)
if VNMNetwork::COMMANDS.keys.include?(cmd.to_sym)
cmd_str = "#{VNMNetwork::COMMANDS[cmd.to_sym]}"
else
cmd_str = "#{cmd}"
end
return cmd_str
end
end
end

View File

@ -0,0 +1,49 @@
# -------------------------------------------------------------------------- #
# 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 'command'
require 'vm'
require 'nic'
require 'address'
require 'security_groups'
require 'security_groups_iptables'
require 'vnm_driver'
require 'fw_driver'
require 'sg_driver'
require 'scripts_common'
include OpenNebula
begin
CONF = YAML.load_file(
File.join(File.dirname(__FILE__), "OpenNebulaNetwork.conf")
)
rescue
CONF = {
:start_vlan => 2
}
end
# Set PATH
ENV['PATH'] = "#{ENV['PATH']}:/bin:/sbin:/usr/bin"

View File

@ -14,9 +14,9 @@
# limitations under the License. #
#--------------------------------------------------------------------------- #
require 'OpenNebulaNetwork'
require 'vnmmad'
class OpenvSwitchVLAN < OpenNebulaNetwork
class OpenvSwitchVLAN < VNMMAD::VNMDriver
DRIVER = "ovswitch"
FIREWALL_PARAMS = [:black_ports_tcp,
@ -91,7 +91,7 @@ class OpenvSwitchVLAN < OpenNebulaNetwork
end
def tag_vlan
cmd = "#{COMMANDS[:ovs_vsctl]} set Port #{@nic[:tap]} "
cmd = "#{command(:ovs_vsctl)} set Port #{@nic[:tap]} "
cmd << "tag=#{vlan}"
run cmd
@ -100,7 +100,7 @@ class OpenvSwitchVLAN < OpenNebulaNetwork
def tag_trunk_vlans
range = @nic[:vlan_tagged_id]
if range? range
ovs_vsctl_cmd = "#{COMMANDS[:ovs_vsctl]} set Port #{@nic[:tap]}"
ovs_vsctl_cmd = "#{command(:ovs_vsctl)} set Port #{@nic[:tap]}"
cmd = "#{ovs_vsctl_cmd} trunks=#{range}"
run cmd
@ -159,7 +159,7 @@ class OpenvSwitchVLAN < OpenNebulaNetwork
def del_flows
in_port = ""
dump_flows = "#{COMMANDS[:ovs_ofctl]} dump-flows #{@nic[:bridge]}"
dump_flows = "#{command(:ovs_ofctl)} dump-flows #{@nic[:bridge]}"
`#{dump_flows}`.lines do |flow|
next unless flow.match("#{@nic[:mac]}")
flow = flow.split.select{|e| e.match(@nic[:mac])}.first
@ -175,13 +175,13 @@ class OpenvSwitchVLAN < OpenNebulaNetwork
def add_flow(filter,action,priority=nil)
priority = (priority.to_s.empty? ? "" : "priority=#{priority},")
run "#{COMMANDS[:ovs_ofctl]} add-flow " <<
run "#{command(:ovs_ofctl)} add-flow " <<
"#{@nic[:bridge]} #{filter},#{priority}actions=#{action}"
end
def del_flow(filter)
filter.gsub!(/priority=(\d+)/,"")
run "#{COMMANDS[:ovs_ofctl]} del-flows " <<
run "#{command(:ovs_ofctl)} del-flows " <<
"#{@nic[:bridge]} #{filter}"
end
@ -192,7 +192,7 @@ class OpenvSwitchVLAN < OpenNebulaNetwork
def port
return @nic[:port] if @nic[:port]
dump_ports = `#{COMMANDS[:ovs_ofctl]} \
dump_ports = `#{command(:ovs_ofctl)} \
dump-ports #{@nic[:bridge]} #{@nic[:tap]}`
@nic[:port] = dump_ports.scan(/^\s*port\s*(\d+):/).flatten.first

View File

@ -0,0 +1,146 @@
#!/usr/bin/env ruby
# -------------------------------------------------------------------------- #
# 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.dirname(__FILE__) + '/../lib'
$: << File.dirname(__FILE__) + '/../../../mad/ruby'
require 'vnmmad'
module VNMMAD
module VNMNetwork
class Nics < Array
def initialize(hypervisor)
@nicClass = NicTest
end
end
class NicTest < Hash
def initialize
super(nil)
end
def get_info(vm)
end
def get_tap(vm)
self[:tap] = "vnet0"
self
end
end
class Commands < Array
def run!
self.each{ |c| puts "#{c}"}
clear
return ""
end
end
end
end
vm_xml=<<EOF
<VM>
<ID>3</ID>
<TEMPLATE>
<NIC>
<AR_ID><![CDATA[0]]></AR_ID>
<BRIDGE><![CDATA[vbr0]]></BRIDGE>
<FILTER_IP_SPOOFING><![CDATA[YES]]></FILTER_IP_SPOOFING>
<FILTER_MAC_SPOOFING><![CDATA[YES]]></FILTER_MAC_SPOOFING>
<IP><![CDATA[10.0.0.7]]></IP>
<MAC><![CDATA[02:00:0a:00:00:07]]></MAC>
<NETWORK><![CDATA[test]]></NETWORK>
<NETWORK_ID><![CDATA[0]]></NETWORK_ID>
<NETWORK_UNAME><![CDATA[ruben]]></NETWORK_UNAME>
<NIC_ID><![CDATA[0]]></NIC_ID>
<SECURITY_GROUPS><![CDATA[100]]></SECURITY_GROUPS>
<VLAN><![CDATA[NO]]></VLAN>
</NIC>
<SECURITY_GROUP_RULE>
<PROTOCOL><![CDATA[TCP]]></PROTOCOL>
<RULE_TYPE><![CDATA[outbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<PROTOCOL><![CDATA[TCP]]></PROTOCOL>
<RANGE><![CDATA[80,22]]></RANGE>
<RULE_TYPE><![CDATA[inbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<ICMP_TYPE><![CDATA[8]]></ICMP_TYPE>
<PROTOCOL><![CDATA[ICMP]]></PROTOCOL>
<RULE_TYPE><![CDATA[inbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<AR_ID><![CDATA[0]]></AR_ID>
<ICMP_TYPE><![CDATA[0]]></ICMP_TYPE>
<IP><![CDATA[10.0.0.7]]></IP>
<MAC><![CDATA[02:00:0a:00:00:07]]></MAC>
<NETWORK_ID><![CDATA[0]]></NETWORK_ID>
<PROTOCOL><![CDATA[ICMP]]></PROTOCOL>
<RULE_TYPE><![CDATA[outbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
<SIZE><![CDATA[27]]></SIZE>
<TYPE><![CDATA[IP4]]></TYPE>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<IP><![CDATA[192.168.10.3]]></IP>
<PROTOCOL><![CDATA[TCP]]></PROTOCOL>
<RANGE><![CDATA[80:100,22]]></RANGE>
<RULE_TYPE><![CDATA[inbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
<SIZE><![CDATA[23]]></SIZE>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<AR_ID><![CDATA[0]]></AR_ID>
<ICMP_TYPE><![CDATA[3]]></ICMP_TYPE>
<IP><![CDATA[10.0.0.7]]></IP>
<MAC><![CDATA[02:00:0a:00:00:07]]></MAC>
<NETWORK_ID><![CDATA[0]]></NETWORK_ID>
<PROTOCOL><![CDATA[ICMP]]></PROTOCOL>
<RULE_TYPE><![CDATA[outbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
<SIZE><![CDATA[27]]></SIZE>
<TYPE><![CDATA[IP4]]></TYPE>
</SECURITY_GROUP_RULE>
<SECURITY_GROUP_RULE>
<IP><![CDATA[172.168.0.0]]></IP>
<PROTOCOL><![CDATA[UDP]]></PROTOCOL>
<RULE_TYPE><![CDATA[outbound]]></RULE_TYPE>
<SECURITY_GROUP_ID><![CDATA[100]]></SECURITY_GROUP_ID>
<SECURITY_GROUP_NAME><![CDATA[Test]]></SECURITY_GROUP_NAME>
<SIZE><![CDATA[255]]></SIZE>
</SECURITY_GROUP_RULE>
<TEMPLATE_ID><![CDATA[0]]></TEMPLATE_ID>
<VMID><![CDATA[0]]></VMID>
</TEMPLATE>
</VM>
EOF
one_sg = VNMMAD::SGDriver.new(vm_xml, "one-0", "test")
one_sg.activate

View File

@ -34,9 +34,9 @@ $: << RUBY_LIB_LOCATION
require 'yaml'
require 'CommandManager'
require 'OpenNebulaNetwork'
require 'vnmmad'
class OpenNebulaVMware < OpenNebulaNetwork
class OpenNebulaVMware < VNMMAD::VNMDriver
DRIVER = "vmware"
XPATH_FILTER = "TEMPLATE/NIC"