Skip to content

Commit

Permalink
B OpenNebula#3354: implement spoofing filters
Browse files Browse the repository at this point in the history
 handle ARP + Alias IPs
 handle IPv4 + Alias IPs
 handle IPv6 + Alias IPs, SLAAC
 handle HOTPLUG_NIC events for Alias IPs via a hook
  • Loading branch information
atodorov-storpool committed Jan 7, 2021
1 parent ff41ea8 commit f4f76b7
Show file tree
Hide file tree
Showing 13 changed files with 547 additions and 1 deletion.
16 changes: 15 additions & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -633,11 +633,15 @@ INSTALL_FILES=(
NETWORK_HOOKS_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/hooks/clean
NETWORK_ETC_FILES:$VAR_LOCATION/remotes/etc/vnm
NETWORK_8021Q_FILES:$VAR_LOCATION/remotes/vnm/802.1Q
NETWORK_8021Q_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/802.1Q/clean.d
NETWORK_8021Q_POST_FILES:$VAR_LOCATION/remotes/vnm/802.1Q/post.d
NETWORK_VXLAN_FILES:$VAR_LOCATION/remotes/vnm/vxlan
NETWORK_DUMMY_FILES:$VAR_LOCATION/remotes/vnm/dummy
NETWORK_BRIDGE_FILES:$VAR_LOCATION/remotes/vnm/bridge
NETWORK_EBTABLES_FILES:$VAR_LOCATION/remotes/vnm/ebtables
NETWORK_FW_FILES:$VAR_LOCATION/remotes/vnm/fw
NETWORK_FW_CLEAN_FILES:$VAR_LOCATION/remotes/vnm/fw/clean.d
NETWORK_FW_POST_FILES:$VAR_LOCATION/remotes/vnm/fw/post.d
NETWORK_OVSWITCH_FILES:$VAR_LOCATION/remotes/vnm/ovswitch
NETWORK_OVSWITCH_VXLAN_FILES:$VAR_LOCATION/remotes/vnm/ovswitch_vxlan
NETWORK_VCENTER_FILES:$VAR_LOCATION/remotes/vnm/vcenter
Expand Down Expand Up @@ -1478,6 +1482,7 @@ NETWORK_FILES="src/vnm_mad/remotes/lib/vnm_driver.rb \
src/vnm_mad/remotes/lib/no_vlan.rb \
src/vnm_mad/remotes/lib/security_groups.rb \
src/vnm_mad/remotes/lib/security_groups_iptables.rb \
src/vnm_mad/remotes/lib/vnm_filter.rb \
src/vnm_mad/remotes/lib/nic.rb"

NETWORK_HOOKS_PRE_FILES="src/vnm_mad/remotes/hooks/pre/firecracker"
Expand All @@ -1490,6 +1495,10 @@ NETWORK_8021Q_FILES="src/vnm_mad/remotes/802.1Q/clean \
src/vnm_mad/remotes/802.1Q/update_sg \
src/vnm_mad/remotes/802.1Q/vlan_tag_driver.rb"

NETWORK_8021Q_CLEAN_FILES="src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean"

NETWORK_8021Q_POST_FILES="src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post"

NETWORK_VXLAN_FILES="src/vnm_mad/remotes/vxlan/clean \
src/vnm_mad/remotes/vxlan/post \
src/vnm_mad/remotes/vxlan/pre \
Expand Down Expand Up @@ -1519,6 +1528,10 @@ NETWORK_FW_FILES="src/vnm_mad/remotes/fw/post \
src/vnm_mad/remotes/fw/update_sg \
src/vnm_mad/remotes/fw/clean"

NETWORK_FW_CLEAN_FILES="src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean"

NETWORK_FW_POST_FILES="src/vnm_mad/remotes/fw/post.d/vnm_filter_post"

NETWORK_OVSWITCH_FILES="src/vnm_mad/remotes/ovswitch/clean \
src/vnm_mad/remotes/ovswitch/post \
src/vnm_mad/remotes/ovswitch/pre \
Expand Down Expand Up @@ -2026,7 +2039,8 @@ HOOK_RAFT_FILES="share/hooks/raft/vip.sh"
# HOOK scripts, to be installed under $VAR_LOCATION/remotes/hooks/alias_ip
#-------------------------------------------------------------------------------

HOOK_ALIAS_IP_FILES="share/hooks/alias_ip/alias_ip.rb"
HOOK_ALIAS_IP_FILES="share/hooks/alias_ip/alias_ip.rb \
share/hooks/alias_ip/vnm_filter.rb"

#-------------------------------------------------------------------------------
# Installation scripts, to be installed under $SHARE_LOCATION
Expand Down
194 changes: 194 additions & 0 deletions share/hooks/alias_ip/vnm_filter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2019, OpenNebula Project, OpenNebula Systems #
# #
# 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. #
#--------------------------------------------------------------------------- #

# hook definition
#
#NAME = "vnm_filter"
#TYPE = "state"
#ON = "CUSTOM"
#ARGUMENTS = "$TEMPLATE"
#ARGUMENTS_STDIN="YES"
#COMMAND="alias_ip/vnm_filter.rb"
#REMOTE="YES"
#RESOURCE="VM"
#STATE="ACTIVE"
#LCM_STATE="HOTPLUG_NIC"

ONE_LOCATION = ENV['ONE_LOCATION']

if !ONE_LOCATION
RUBY_LIB_LOCATION = '/usr/lib/one/ruby'
GEMS_LOCATION = '/usr/share/one/gems'
PACKET_LOCATION = '/usr/lib/one/ruby/vendors/packethost/lib'
LOG_FILE = '/var/log/one/hook-alias_ip.log'
else
RUBY_LIB_LOCATION = ONE_LOCATION + '/lib/ruby'
GEMS_LOCATION = ONE_LOCATION + '/share/gems'
PACKET_LOCATION = ONE_LOCATION + '/ruby/vendors/packethost/lib'
LOG_FILE = ONE_LOCATION + '/var/net_fw_hook.log'
end

if File.directory?(GEMS_LOCATION)
Gem.use_paths(GEMS_LOCATION)
end

$LOAD_PATH << RUBY_LIB_LOCATION
$LOAD_PATH << PACKET_LOCATION

require 'base64'
require 'nokogiri'
require 'open3'
require 'shellwords'
require 'syslog/logger'

##########
# Helpers

@slog = Syslog::Logger.new 'vnm_filter_hook'

def log(msg, level = 'I')
msg.lines do |line|
puts(line)
@slog.info "[#{level}] #{line}"
end
end

def log_error(msg)
log(msg, 'E')
end

def get_data(xpath, entries)
data = Hash.new
xentry = VM_XML.xpath(xpath)
entries.each do |e|
val = xentry.xpath(e)
if !val.nil?
key = e.downcase.to_sym
if e.end_with?("_ID")
data[key] = val.text.to_i
else
data[key] = val.text
end
end
end
data
end

def alias_nic_data()
xpath = '//TEMPLATE/NIC_ALIAS[ATTACH="YES"]'
entries = %w[ALIAS_ID PARENT_ID NAME IP IP6 IP6_GLOBAL IP6_LINK]
get_data(xpath, entries)
end

def nic_data(nic_id)
xpath = "//TEMPLATE/NIC[NIC_ID=#{nic_id}]"
entries = %w[IP IP6 IP6_GLOBAL IP6_LINK VN_MAD ALIAS_IDS
FILTER FILTER_IP_SPOOFING FILTER_MAC_SPOOFING]
get_data(xpath, entries)
end

def vm_data()
vm = Hash.new
data = get_data("//VM", %w[ID])
vm[:id] = VM_XML.xpath('//VM/ID').text.to_i
vm[:domain] = "one-#{vm[:id]}"
vm[:a] = alias_nic_data()
nic_id = vm[:a][:parent_id]
vm[:n] = nic_data(nic_id)

vm[:nicdev] = "#{vm[:domain]}-#{nic_id}"
vm[:a][:idx] = vm[:a][:name].split('_ALIAS')[1].to_i

vm[:action] = 'del'
if !vm[:n][:alias_ids].nil? and !vm[:n][:alias_ids].empty?
vm[:n][:alias_ids].split(',').each do |idx|
if vm[:a][:idx] == idx.to_i
vm[:action] = 'add'
end
end
end
#log("#{vm}")
vm
end

def run(cmds)
cmd = String.new
cmds.each do |c|
cmd.concat(" #{Shellwords.escape(c)}")
end
stdout, stderr, status = Open3.capture3(cmd)
log("(#{status.exitstatus}) #{cmd}")
if !status.success?
log_error("PID[#{status.pid}] #{stderr}")
end
end

def toggle_ebtables_filter(vm)
if !vm[:a][:ip].nil? and !vm[:a][:ip].empty?
action = vm[:action]=='add'? '-A' : '-D'
['i', 'o'].each do |d|
rule = d=='o'? '--arp-ip-dst' : '--arp-ip-src'
chain = "#{vm[:nicdev]}-#{d}-arp4"
run(['sudo', 'ebtables', '--concurrent', '-t', 'nat', action,
chain, '-p', 'ARP', rule, vm[:a][:ip], '-j', 'RETURN'])
end
end
end

def toggle_ipset_filter(vm)
['IP', 'IP6', 'IP6_GLOBAL'].each do |e|
key = e.downcase.to_sym
if !vm[:a][key].nil? and !vm[:a][key].empty?
chain = "#{vm[:nicdev]}-#{e.split('_')[0].downcase}-spoofing"
run(['sudo', 'ipset', '-exist', vm[:action], chain, vm[:a][key]])
if e == 'IP6_CLOBAL' and !vm[:a][:ip6_link].nil?
link = vm[:a][:ip6_link]
run(['sudo', 'ipset', '-exist', vm[:action], chain, link])
end
end
end
end


###############################################################################
# Main
#

log("Net fw hook BEGIN")

vm_xml_raw = Base64.decode64(STDIN.read)
vm_xml = Nokogiri::XML(vm_xml_raw)
VM_XML = vm_xml

vm = vm_data()

filters = Hash.new
filters[:filter_ip_spoofing] = method(:toggle_ipset_filter)
filters[:filter_mac_spoofing] = method(:toggle_ebtables_filter)

filters.each do |key, method|
if !vm[:n][key].nil?
if vm[:n][key] == 'YES'
method.(vm)
end
end
end

log('Net fw hook END')

exit 0
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/802.1Q/clean.d/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Do not track files in this directory except for .gitignore file
*
!.gitignore
!vnm_filter_clean
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/802.1Q/clean.d/vnm_filter_clean
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/802.1Q/post.d/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Do not track files in this directory except for .gitignore file
*
!.gitignore
!vnm_filter_post
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/802.1Q/post.d/vnm_filter_post
36 changes: 36 additions & 0 deletions src/vnm_mad/remotes/common/vnm_filter_clean
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2019, OpenNebula Project, OpenNebula Systems #
# #
# 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__), "..")
$: << File.join(File.dirname(__FILE__), "../..")

require 'vnm_filter'

template64 = STDIN.read
deploy_id = nil
xpath_filter = nil

begin
vnmfilter = VnmFilter.from_base64(template64, xpath_filter, deploy_id)
vnmfilter.deactivate
rescue Exception => e
OpenNebula.log_error(e.message)
OpenNebula.log_error(e.backtrace)
exit 1
end
36 changes: 36 additions & 0 deletions src/vnm_mad/remotes/common/vnm_filter_post
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
#!/usr/bin/env ruby

# -------------------------------------------------------------------------- #
# Copyright 2002-2019, OpenNebula Project, OpenNebula Systems #
# #
# 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__), "..")
$: << File.join(File.dirname(__FILE__), "../..")

require 'vnm_filter'

template64 = STDIN.read
deploy_id = nil
xpath_filter = nil

begin
vnmfilter = VnmFilter.from_base64(template64, xpath_filter, deploy_id)
vnmfilter.activate
rescue Exception => e
OpenNebula.log_error(e.message)
OpenNebula.log_error(e.backtrace)
exit 1
end
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/fw/clean.d/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Do not track files in this directory except for .gitignore file
*
!.gitignore
!vnm_filter_clean
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/fw/clean.d/vnm_filter_clean
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/fw/post.d/.gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
# Do not track files in this directory except for .gitignore file
*
!.gitignore
!vnm_filter_post
1 change: 1 addition & 0 deletions src/vnm_mad/remotes/fw/post.d/vnm_filter_post
Loading

0 comments on commit f4f76b7

Please sign in to comment.