[libvirt] [RFC] libvirt-TCK scripts to verify spoofing prevention
Gerhard Stenzel
gstenzel at linux.vnet.ibm.com
Fri Apr 23 14:25:49 UTC 2010
Daniel, thanks for the comments .. most of them are integrated, but I
have a problem with changing MAC addresses, because the domain xml is
regenerated every time, so I cannot rely on the interface name being
"eth0" as fedora during boot creates a new ethX for each new MAC
address .. any good advice?
On Thu, 2010-04-15 at 15:27 +0100, Daniel P. Berrange wrote:
> On Thu, Apr 15, 2010 at 02:35:41PM +0200, Gerhard Stenzel wrote:
> > The following patch mainly adds a set of test case to verify that
> > several spoofing attacks are prevented by the nwfilter subsystem.
> >
> > In order to have a well defined test machine, the patch also includes
> > test scripts to network install a virtual disk from scratch, to boot the
> > virtual test machine prior to running the actual test scripts and to
> > shut it down afterwards.
> >
> > While I have tried to remove as much dependency on my local setup as
> > possible there is still some left, so I am currently more interested in
> > feedback about the general approach, not necessarily actual inclusion
> > into the libvirt-TCK git.
>
> Your actual test cases look good, so I'll just put comments about
> the setup/teardown stuff inline.
ok .. sounds good
>
> >
> > For example, I am currently trying to find a suitable location for the
> > kickstart file, and also a suitable place for the common_functions.pl.
>
> The 'lib' directory contains modules which provide common functions &
> code for the test scripts. In this case I'd suggest creating a file
>
> lib/Sys/Virt/TCK/NetworkHelpers.pm (use Sys::Virt::TCK::NetworkHelpers)
ok .. done
>
> > Index: libvirt-tck/scripts/network/README
> > ===================================================================
> > --- /dev/null
> > +++ libvirt-tck/scripts/network/README
> > @@ -0,0 +1,14 @@
> > +
> > +Test cases:
> > +
> > +000-install-image.t creates and install a 2GB fedora virtual disk via
> > kickstart file from the network
> > +001-boot-image.t defines and boots a VM which uses the fedora virtual
> > disk
> > +100-ping-still-working.t verifies the VM is pingable
> > +210-no-mac-spoofing.t verifies mac spoofing is prevented
> > +220-no-ip-spoofing.t verifies ip spoofing is prevented
> > +230-no-mac-broadcast.t verifies mac broadcasting is prevented
> > +240-no-arp-spoofing.t verifies arp spoofing is prevented
> > +999-shutdown-image.t shuts the VM down
>
> One thing about the TCK test cases is that each one should be
> self-contained, doing all setup & teardown it requires, not
> reliant on any of the other tests cases or ordering of tests.
>
> So instead of having the 000-install-image.t & 0001-boot-image.t
> scripts that do setup, you'd want to create some library code
> that can be used to install + boot the guest, and just call that
> from each test case.
I am currently trying this .. however, what I am struggling with is that
the MAC address is different for every boot.
>
> > Index: libvirt-tck/scripts/network/000-install-image.t
> > ===================================================================
> > --- /dev/null
> > +++ libvirt-tck/scripts/network/000-install-image.t
> > @@ -0,0 +1,181 @@
> > +# -*- perl -*-
> > +#
> > +# Copyright (C) 2010 IBM Corp.
> > +#
> > +# This program is free software; You can redistribute it and/or modify
> > +# it under the GNU General Public License as published by the Free
> > +# Software Foundation; either version 2, or (at your option) any
> > +# later version
> > +#
> > +# The file "LICENSE" distributed along with this file provides full
> > +# details of the terms and conditions
> > +#
> > +
> > +=pod
> > +
> > +=head1 NAME
> > +
> > +network/000-install-image.t - install network test image
> > +
> > +=head1 DESCRIPTION
> > +
> > +The test case creates and install a 2GB fedora virtual
> > +disk via kickstart file from the network.
> > +
> > +=cut
> > +
> > +use strict;
> > +use warnings;
> > +
> > +use Test::More tests => 1;
> > +
> > +use Sys::Virt::TCK;
> > +
> > +my $tck = Sys::Virt::TCK->new();
> > +my $conn = eval { $tck->setup(); };
> > +BAIL_OUT "failed to setup test harness: $@" if $@;
> > +END { $tck->cleanup if $tck; }
> > +
> > +# variables which may need to be adapted
> > +my $domain_name ="f12nwtest";
> > +my $disk_name = "/var/lib/libvirt/images/${domain_name}.img";
> > +my $disk_size = "2147483648";
>
> You want to avoid hardcoding any disk paths in the test cases.
>
> There's a convenient helper function if you want to create a
> scratch disk for a guest
>
> my $path = $tck->create_sparse_disk("nwfilter", "root.img", 2147483648);
create_sparse_disk does not work for me, anaconda does not recognize
installable disks .. anyway I got rid of the hardcoded names.
>
> > +my $kickstart_file ="http://192.168.122.1/ks.cfg";
> > +my $cmdline = "ip=dhcp gateway=192.168.122.1 ks=${kickstart_file}";
>
> In the 'conf/default.cfg' file we list the kernel+initrd images for
> various distros. We could likely also have an optional kickstart
> file listed there, so we move this configuration info out of the test
> cases.
sounds good .. but not done yet
>
> > +# see if the domain already exits
> > +my $already_defined = 0;
> > +diag "searching if ${domain_name} is already defined";
> > +my $nnames = $conn->num_of_defined_domains();
> > +my @names = $conn->list_defined_domain_names($nnames);
> > +foreach (@names){
> > + if (/${domain_name}/) {
> > + print "$_ already exists, no need to redefine\n";
> > + $already_defined = 1;
> > + }
> > +}
> > +diag $already_defined;
>
> All guests created by the TCK test cases should have a name prefix
> of 'tck' to avoid clashing with existing guests. Each test case
> should destroy + undefine all guests it creates. The TCK setup
> code will validate that there are no guests having a name starting
> with a 'tck' prefix & optionally destroy them. So each of your
> test cases should assume the guest doesn't already exist + clean
> it up before exiting.
ok .. working on that one
>
> NB, the disk files are not deleted between runs, so you will
> still avoid the overhead of installation.
>
>
> I'm talking with the libguestfs folks to see if we can re-use
> their guest appliance + daemon so we can avoid the actual install
> + ssh stuff at some point in the future.
>
> > +# check for installation disk and build it if not exists
> > +my $already_installed = 0;
> > +my $pool = $conn->get_storage_pool_by_name("default");
> > +my $nnames = $pool->num_of_storage_volumes();
> > +my @volNames = $pool->list_storage_vol_names($nnames);
> > +foreach (@volNames){
> > + if (/${domain_name}/) {
> > + print "$_ already exists, no need to install\n";
> > + $already_installed = 1;
> > + }
> > +}
> > +
> > +my $volumexml = "<volume>".
> > +" <name>${domain_name}.img</name>".
> > +" <key>${disk_name}</key>".
> > +" <source>".
> > +" </source>".
> > +" <capacity>${disk_size}</capacity>".
> > +" <allocation>4096</allocation>".
> > +" <target>".
> > +" <path>${disk_name}</path>".
> > +" <format type='raw'/>".
> > +" <permissions>".
> > +" <mode>0644</mode>".
> > +" <owner>0</owner>".
> > +" <group>0</group>".
> > +" </permissions>".
> > +" </target>".
> > +"</volume>";
>
>
> If using the create_sparse_disk helper I mentioned, you can avoid this
> chunk of code. Just do a check to see if $path exists or not.
ok . solved differently with generic_volume()
>
> > +# prepare image
> > +if ($already_installed == 0) {
> > + diag "Creating ${disk_name}";
> > + diag $volumexml;
> > + my $vol = $pool->create_volume($volumexml)
> > +# system("qemu-img create ${disk_name} ${disk_size}");
> > +}
> > +
> > +my $topxml = " <name>${domain_name}</name>".
> > +" <memory>524288</memory>".
> > +" <currentMemory>524288</currentMemory>".
> > +" <vcpu>1</vcpu>";
> > +
> > +my $osxml = " <os>".
> > +" <type arch='x86_64' machine='fedora-13'>hvm</type>".
> > +" <kernel>/var/cache/libvirt-tck/os-i686-hvm/vmlinuz</kernel>".
> > +" <initrd>/var/cache/libvirt-tck/os-i686-hvm/initrd</initrd>".
> > +" <cmdline>${cmdline}</cmdline>".
> > +" <boot dev='hd'/>".
> > +" </os>";
> > +
> > +my $bottomxml = " <features>".
> > +" <acpi/>".
> > +" <apic/>".
> > +" </features>".
> > +" <clock offset='utc'/>".
> > +" <on_poweroff>destroy</on_poweroff>".
> > +" <on_reboot>restart</on_reboot>".
> > +" <on_crash>restart</on_crash>".
> > +" <devices>".
> > +" <emulator>/usr/bin/qemu-kvm</emulator>".
> > +" <disk type='file' device='disk'>".
> > +" <driver name='qemu' type='raw'/>".
> > +" <source file='${disk_name}'/>".
> > +" <target dev='hda' bus='ide'/>".
> > +" </disk>".
> > +" <controller type='ide' index='0'>".
> > +" </controller>".
> > +" <interface type='network'>".
> > +" <source network='default'/>".
> > +" <target dev='vnet0'/>".
> > +" <model type='virtio'/>".
> > +" </interface>".
> > +" <serial type='pty'>".
> > +" <target port='0'/>".
> > +" </serial>".
> > +" <console type='pty'>".
> > +" <target port='0'/>".
> > +" </console>".
> > +" <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'
> > keymap='de'/>".
> > +" <video>".
> > +" <model type='cirrus' vram='9216' heads='1'/>".
> > +" </video>".
> > +" </devices>";
> > +
> > +my $xml = "<domain type='kvm'>" .
> > +$topxml.
> > +$osxml.
> > +$bottomxml.
> > +"</domain>";
>
> There are a couple of helpers for building XML in a portable manner.
> In the Sys::Virt::TCK class there's a method 'generic_domain' which
> will find you a bare minimum VM matching the best kernel / arch
> found in the TCK config file. It returns an instance of the the
> class Sys::Virt::TCK::DomainBuilder which lets you then add
> extra XML to the guest.
>
> eg, so you could do
>
> my $guest = $tck->generic_domain();
>
> $guest->boot_cmdline($cmdline);
>
>
> The 'boot_cmdline' method doesn't exist yet, but feel frree to extend
> the shared helper modules as needed. Its much nicer to use them than
> to build up XML manually.
ok .. done
>
> > +
> > +diag $xml;
> > +diag "Defining an inactive domain config";
> > +my $dom;
> > +
> > +# no need to start if already installed
> > +if (($already_installed == 0) && ($already_defined == 0)) {
> > + ok_domain(sub { $dom = $conn->define_domain($xml) }, "defined
> > persistent domain config");
> > + $xml = $dom->get_xml_description;
> > + diag $xml;
> > + diag "Starting inactive domain config";
> > + $dom->create;
> > +
> > + # wait for completion of installation
> > + diag "wait for installation to finish .. ";
> > + while($dom->is_active()) {
> > + sleep(10);
> > + diag ".. to view progress connect to virtual machine ${domain_name} ..
> > ";
> > + }
> > + sleep(10);
> > + diag " .. done";
> > + # cleanup
> > + $dom->undefine;
> > +} else {
> > + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the
> > existing domain object";
> > +}
> > +
> > +
> > +
> > +
> > +
> > Index: libvirt-tck/scripts/network/001-boot-image.t
> > ===================================================================
> > --- /dev/null
> > +++ libvirt-tck/scripts/network/001-boot-image.t
> > @@ -0,0 +1,133 @@
> > +# -*- perl -*-
> > +#
> > +# Copyright (C) 2010 IBM Corp.
> > +#
> > +# This program is free software; You can redistribute it and/or modify
> > +# it under the GNU General Public License as published by the Free
> > +# Software Foundation; either version 2, or (at your option) any
> > +# later version
> > +#
> > +# The file "LICENSE" distributed along with this file provides full
> > +# details of the terms and conditions
> > +#
> > +
> > +=pod
> > +
> > +=head1 NAME
> > +
> > +network/001-boot-image.t - boot installed test image
> > +
> > +=head1 DESCRIPTION
> > +
> > +The test case defines and boots a VM which uses the
> > +fedora virtual disk create ny 000-install-image
> > +
> > +=cut
> > +
> > +use strict;
> > +use warnings;
> > +
> > +use Test::More tests => 2;
> > +
> > +use Sys::Virt::TCK;
> > +use XML::LibXML;
> > +
> > +require 'scripts/network/common_functions.pl';
> > +
> > +my $tck = Sys::Virt::TCK->new();
> > +my $conn = eval { $tck->setup(); };
> > +BAIL_OUT "failed to setup test harness: $@" if $@;
> > +END { $tck->cleanup if $tck; }
> > +
> > +my $already_defined = 0;
> > +my $domain_name ="f12nwtest";
> > +
> > +# see if the domain already exits
> > +diag "searching if ${domain_name} is already defined";
> > +my $nnames = $conn->num_of_defined_domains();
> > +my @names = $conn->list_defined_domain_names($nnames);
> > +foreach (@names){
> > + if (/${domain_name}/) {
> > + print "$_ already exists, no need to redefine\n";
> > + $already_defined = 1;
> > + }
> > +}
> > +diag $already_defined;
> > +
> > +my $dom;
> > +my $xml;
> > +
> > +if ($already_defined == 1) {
> > + ok_domain { $dom = $conn->get_domain_by_name($domain_name) } "the
> > existing domain object";
> > +} else {
> > + my $topxml = " <name>${domain_name}</name>".
> > + " <memory>524288</memory>".
> > + " <currentMemory>524288</currentMemory>".
> > + " <vcpu>1</vcpu>";
> > +
> > + my $osxml = " <os>".
> > + " <type arch='x86_64' machine='fedora-13'>hvm</type>".
> > + " <boot dev='hd'/>".
> > + " </os>";
> > +
> > + my $bottomxml = " <features>".
> > + " <acpi/>".
> > + " <apic/>".
> > + " </features>".
> > + " <clock offset='utc'/>".
> > + " <on_poweroff>destroy</on_poweroff>".
> > + " <on_reboot>restart</on_reboot>".
> > + " <on_crash>restart</on_crash>".
> > + " <devices>".
> > + " <emulator>/usr/bin/qemu-kvm</emulator>".
> > + " <disk type='file' device='disk'>".
> > + " <driver name='qemu' type='raw'/>".
> > + " <source file='/var/lib/libvirt/images/${domain_name}.img'/>".
> > + " <target dev='hda' bus='ide'/>".
> > + " </disk>".
> > + " <controller type='ide' index='0'>".
> > + " </controller>".
> > + " <interface type='network'>".
> > + " <source network='default'/>".
> > + " <filterref filter='no-spoofing'/>".
> > + " <target dev='vnet0'/>".
> > + " <model type='virtio'/>".
> > + " </interface>".
> > + " <serial type='pty'>".
> > + " <target port='0'/>".
> > + " </serial>".
> > + " <console type='pty'>".
> > + " <target port='0'/>".
> > + " </console>".
> > + " <graphics type='vnc' port='-1' autoport='yes' listen='127.0.0.1'
> > keymap='de'/>".
> > + " <video>".
> > + " <model type='cirrus' vram='9216' heads='1'/>".
> > + " </video>".
> > + " </devices>";
> > +
> > + $xml = "<domain type='kvm'>" .
> > + $topxml.
> > + $osxml.
> > + $bottomxml.
> > + "</domain>";
>
> Again, I think you'd be better using the '$tck->generic_domain' method,
> and adding support to Sys::Virt::TCK::DomainBuilder for adding the
> '<filterref filter='no-spoofing'/>' bit of the XML.
>
ok .. done
>
>
> > Index: libvirt-tck/scripts/network/100-ping-still-working.t
> > ===================================================================
> > --- /dev/null
> > +++ libvirt-tck/scripts/network/100-ping-still-working.t
> > @@ -0,0 +1,71 @@
> > +# -*- perl -*-
> > +#
> > +# Copyright (C) 2010 IBM Corp.
> > +#
> > +# This program is free software; You can redistribute it and/or modify
> > +# it under the GNU General Public License as published by the Free
> > +# Software Foundation; either version 2, or (at your option) any
> > +# later version
> > +#
> > +# The file "LICENSE" distributed along with this file provides full
> > +# details of the terms and conditions
> > +#
> > +
> > +=pod
> > +
> > +=head1 NAME
> > +
> > +network/100-ping-still-working.t - verify machines can be pinged from
> > host
> > +
> > +=head1 DESCRIPTION
> > +
> > +The test case validates that it is possible to ping a guest machine
> > from
> > +the host.
> > +
> > +=cut
> > +
> > +use strict;
> > +use warnings;
> > +
> > +use Test::More tests => 4;
> > +
> > +use Sys::Virt::TCK;
> > +use Test::Exception;
> > +use Net::SSH::Perl;
> > +use XML::LibXML;
> > +
> > +require 'scripts/network/common_functions.pl';
> > +
> > +my $tck = Sys::Virt::TCK->new();
> > +my $conn = eval { $tck->setup(); };
> > +BAIL_OUT "failed to setup test harness: $@" if $@;
> > +END {
> > + $tck->cleanup if $tck;
> > +}
> > +
> > +# create first domain and start it
> > +diag "Trying domain lookup by name";
> > +my $dom1;
> > +my $domain_name ="f12nwtest";
>
> This is where you'd want to start the guest domain
>
> > +
> > +ok_domain { $dom1 = $conn->get_domain_by_name($domain_name) } "the
> > running domain object";
> > +my $xml = $dom1->get_xml_description;
> > +diag $xml;
> > +ok($dom1->get_id() > 0, "running domain has an ID > 0");
> > +my $mac1 = get_macaddress($xml);
> > +diag $mac1;
> > +my $guestip1 = get_ip_from_leases($mac1);
> > +diag "ip is $guestip1";
> > +
> > +# check ebtables entry
> > +my $ebtable1 = `/sbin/ebtables -L;/sbin/ebtables -t nat -L`;
> > +diag $ebtable1;
> > +# fixme to include mac adress
> > +ok($ebtable1 =~ "vnet0", "check ebtables entry");
> > +
> > +# ping guest1
> > +my $ping1 = `ping -c 10 $guestip1`;
> > +diag $ping1;
> > +ok($ping1 =~ "10 received", "ping $guestip1 test");
>
> And run 'destroy' here. If you use a transient guest when
> initially booting ($conn->create_domain($xml)) then you'll
> not have to worry about the undefine step.
>
>
> > +
> > +sub get_macaddress {
> > + my $xmldesc = shift;
> > +
> > + my $mac;
> > + my $parser = XML::LibXML->new();
> > +
> > + my $doc = $parser->parse_string($xmldesc);
> > +
> > + my $rootel = $doc -> getDocumentElement();
> > +
> > + my @devices = $rootel->getChildrenByTagName("devices");
> > + foreach my $device(@devices) {
> > + my @interfaces = $device->getChildrenByTagName("interface");
> > + foreach my $interface(@interfaces) {
> > + my @targets = $interface->getChildrenByTagName("mac");
> > + foreach my $target(@targets) {
> > + $mac = $target->getAttribute("address");
> > + }
> > + }
> > + }
> > + utf8::decode($mac);
> > + return $mac;
> > +}
>
> You can simplify this using XPath - the $tck object provides a
> convenient method for performing an XPath query on a $dom object.
> eg that whole method can just be
>
> my $resultt = xpath($dom, "/domain/devices/interface/mac/\@address")
> my @macaddrs = map { $_->getNodeValue} $result->get_nodelist;
>
ok .. but not done yet
>
> Daniel
--
Best regards,
Gerhard Stenzel,
-----------------------------------------------------------------------------------------------------------------------------------
IBM Deutschland Research & Development GmbH
Vorsitzender des Aufsichtsrats: Martin Jetter
Geschäftsführung: Dirk Wittkopp
Sitz der Gesellschaft: Böblingen
Registergericht: Amtsgericht Stuttgart, HRB 243294
More information about the libvir-list
mailing list