[libvirt] [PATCH 07/19] Auto-generate helpers for checking access control rules

Daniel P. Berrange berrange at redhat.com
Thu May 9 13:26:10 UTC 2013


From: "Daniel P. Berrange" <berrange at redhat.com>

Extend the 'gendispatch.pl' script to be able to generate
three new types of file.

- 'aclheader' - defines signatures of helper APIs for
  doing authorization checks. There is one helper API
  for each API requiring an auth check. Any @acl
  annotations result in a method being generated with
  a suffix of 'EnsureACL'. If the ACL check requires
  examination of flags, an extra 'flags' param will be
  present. Some examples

  extern int virConnectBaselineCPUEnsureACL(void);
  extern int virConnectDomainEventDeregisterEnsureACL(virDomainDefPtr domain);
  extern int virDomainAttachDeviceFlagsEnsureACL(virDomainDefPtr domain, unsigned int flags);

  Any @aclfilter annotations resuilt in a method being
  generated with a suffix of 'CheckACL'.

  extern int virConnectListAllDomainsCheckACL(virDomainDefPtr domain);

  These are used for filtering individual objects from APIs
  which return a list of objects

- 'aclbody' - defines the actual implementation of the
  methods described above. This calls into the access
  manager APIs. A complex example:

    /* Returns: -1 on error (denied==error), 0 on allowed */
    int virDomainAttachDeviceFlagsEnsureACL(virConnectPtr conn,
                                            virDomainDefPtr domain,
                                            unsigned int flags)
    {
        virAccessManagerPtr mgr;
        int rv;

        if (!(mgr = virAccessManagerGetDefault()))
            return -1;

        if ((rv = virAccessManagerCheckDomain(mgr,
                                              conn->driver->name,
                                              domain,
                                              VIR_ACCESS_PERM_DOMAIN_WRITE)) <= 0) {
            if (rv == 0)
                virReportError(VIR_ERR_ACCESS_DENIED, NULL);
            return -1;
        }
        if (((flags & (VIR_DOMAIN_AFFECT_CONFIG|VIR_DOMAIN_AFFECT_LIVE)) == 0) &&
            (rv = virAccessManagerCheckDomain(mgr,
                                              conn->driver->name,
                                              domain,
                                              VIR_ACCESS_PERM_DOMAIN_SAVE)) <= 0) {
            if (rv == 0)
                virReportError(VIR_ERR_ACCESS_DENIED, NULL);
            return -1;
        }
        if (((flags & (VIR_DOMAIN_AFFECT_CONFIG)) == (VIR_DOMAIN_AFFECT_CONFIG)) &&
            (rv = virAccessManagerCheckDomain(mgr,
                                              conn->driver->name,
                                              domain,
                                              VIR_ACCESS_PERM_DOMAIN_SAVE)) <= 0) {
            if (rv == 0)
                virReportError(VIR_ERR_ACCESS_DENIED, NULL);
            return -1;
        }
        return 0;
    }

- 'aclsyms' - generates a linker script to export the
   APIs to drivers. Some examples

  virConnectBaselineCPUEnsureACL;
  virConnectCompareCPUEnsureACL;

Signed-off-by: Daniel P. Berrange <berrange at redhat.com>
---
 .gitignore             |   9 +++
 src/Makefile.am        |  55 ++++++++++++-
 src/rpc/gendispatch.pl | 210 ++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 269 insertions(+), 5 deletions(-)

diff --git a/.gitignore b/.gitignore
index 76ee420..2deeccf 100644
--- a/.gitignore
+++ b/.gitignore
@@ -105,10 +105,19 @@
 /sc_*
 /src/.*.stamp
 /src/access/org.libvirt.api.policy
+/src/access/viraccessapicheck.c
+/src/access/viraccessapicheck.h
+/src/access/viraccessapichecklxc.c
+/src/access/viraccessapichecklxc.h
+/src/access/viraccessapicheckqemu.c
+/src/access/viraccessapicheckqemu.h
 /src/esx/*.generated.*
 /src/hyperv/*.generated.*
 /src/libvirt*.def
 /src/libvirt.syms
+/src/libvirt_access.syms
+/src/libvirt_access_lxc.syms
+/src/libvirt_access_qemu.syms
 /src/libvirt_*.stp
 /src/libvirt_*helper
 /src/libvirt_*probes.h
diff --git a/src/Makefile.am b/src/Makefile.am
index 20ce083..93af8fd 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -771,6 +771,15 @@ SECURITY_DRIVER_SELINUX_SOURCES =				\
 SECURITY_DRIVER_APPARMOR_SOURCES =				\
 		security/security_apparmor.h security/security_apparmor.c
 
+ACCESS_DRIVER_GENERATED = \
+		access/viraccessapicheck.h access/viraccessapicheck.c \
+		access/viraccessapicheckqemu.h access/viraccessapicheckqemu.c \
+		access/viraccessapichecklxc.h access/viraccessapichecklxc.c
+
+ACCESS_DRIVER_SYMFILES = \
+		libvirt_access.syms \
+		libvirt_access_qemu.syms \
+		libvirt_access_lxc.syms
 
 ACCESS_DRIVER_SOURCES = \
 		access/viraccessperm.h access/viraccessperm.c \
@@ -1361,7 +1370,7 @@ libvirt_security_manager_la_SOURCES += $(SECURITY_DRIVER_APPARMOR_SOURCES)
 libvirt_security_manager_la_CFLAGS += $(APPARMOR_CFLAGS)
 endif
 
-libvirt_driver_access_la_SOURCES = $(ACCESS_DRIVER_SOURCES)
+libvirt_driver_access_la_SOURCES = $(ACCESS_DRIVER_SOURCES) $(ACCESS_DRIVER_GENERATED)
 noinst_LTLIBRARIES += libvirt_driver_access.la
 libvirt_la_BUILT_LIBADD += libvirt_driver_access.la
 libvirt_driver_access_la_CFLAGS = \
@@ -1394,6 +1403,50 @@ EXTRA_DIST += $(ACCESS_DRIVER_POLKIT_SOURCES)
 endif
 
 
+USED_SYM_FILES += $(ACCESS_DRIVER_SYMFILES)
+BUILT_SOURCES += $(ACCESS_DRIVER_GENERATED) $(ACCESS_DRIVER_SYMFILES)
+CLEANFILES += $(ACCESS_DRIVER_GENERATED) $(ACCESS_DRIVER_SYMFILES)
+
+libvirt_access.syms: $(srcdir)/rpc/gendispatch.pl \
+			$(REMOTE_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclsym \
+	  remote REMOTE $(REMOTE_PROTOCOL) > $@
+libvirt_access_qemu.syms: $(srcdir)/rpc/gendispatch.pl \
+			$(QEMU_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclsym \
+	  qemu QEMU $(QEMU_PROTOCOL) > $@
+libvirt_access_lxc.syms: $(srcdir)/rpc/gendispatch.pl \
+			$(LXC_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclsym \
+	  lxc LXC $(LXC_PROTOCOL) > $@
+
+access/viraccessapicheck.h: $(srcdir)/rpc/gendispatch.pl \
+			$(REMOTE_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclheader \
+	  remote REMOTE $(REMOTE_PROTOCOL) > $@
+access/viraccessapicheck.c: $(srcdir)/rpc/gendispatch.pl \
+			$(REMOTE_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclbody \
+	  remote REMOTE $(REMOTE_PROTOCOL) access/viraccessapicheck.h > $@
+
+access/viraccessapicheckqemu.h: $(srcdir)/rpc/gendispatch.pl \
+			$(QEMU_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclheader \
+	  qemu QEMU $(QEMU_PROTOCOL) > $@
+access/viraccessapicheckqemu.c: $(srcdir)/rpc/gendispatch.pl \
+			$(QEMU_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclbody \
+	  qemu QEMU $(QEMU_PROTOCOL) access/viraccessapicheckqemu.h > $@
+
+access/viraccessapichecklxc.h: $(srcdir)/rpc/gendispatch.pl \
+			$(LXC_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclheader \
+	  lxc LXC $(LXC_PROTOCOL) > $@
+access/viraccessapichecklxc.c: $(srcdir)/rpc/gendispatch.pl \
+			$(LXC_PROTOCOL) Makefile.am
+	$(AM_V_GEN)$(PERL) -w $(srcdir)/rpc/gendispatch.pl --mode=aclbody \
+	  lxc LXC $(LXC_PROTOCOL) access/viraccessapichecklxc.h > $@
+
 # Add all conditional sources just in case...
 EXTRA_DIST +=							\
 		$(TEST_DRIVER_SOURCES)				\
diff --git a/src/rpc/gendispatch.pl b/src/rpc/gendispatch.pl
index 0bf76da..1179420 100755
--- a/src/rpc/gendispatch.pl
+++ b/src/rpc/gendispatch.pl
@@ -24,8 +24,9 @@ my $res = GetOptions("mode=s" => \$mode);
 
 die "cannot parse command line options" unless $res;
 
-die "unknown mode '$mode', expecting 'client', 'server' or 'debug'"
-    unless $mode =~ /^(client|server|debug)$/;
+die "unknown mode '$mode', expecting 'client', 'server', " .
+    "'aclheader', 'aclbody', 'aclsym' or 'debug'"
+    unless $mode =~ /^(client|server|aclheader|aclbody|aclsym|debug)$/;
 
 my $structprefix = shift or die "missing struct prefix argument";
 my $procprefix = shift or die "missing procedure prefix argument";
@@ -108,7 +109,13 @@ while (<PROTOCOL>) {
         }
     } elsif ($collect_opts) {
         if (m,^\s*\*\s*\@(\w+)\s*:\s*((?:\w|:|\!|\|)+)\s*$,) {
-            $opts{$1} = $2;
+            if ($1 eq "acl" ||
+                $1 eq "aclfilter") {
+                $opts{$1} = [] unless exists $opts{$1};
+                push @{$opts{$1}}, $2;
+            } else {
+                $opts{$1} = $2;
+            }
         } elsif (m,^\s*\*/\s*$,) {
             $collect_opts = 0;
         } elsif (m,^\s*\*\s*$,) {
@@ -235,6 +242,8 @@ while (<PROTOCOL>) {
             $calls{$name}->{streamflag} = "none";
         }
 
+        $calls{$name}->{acl} = $opts{acl};
+        $calls{$name}->{aclfilter} = $opts{aclfilter};
 
         # for now, we distinguish only two levels of priority:
         # low (0) and high (1)
@@ -321,11 +330,18 @@ sub hyper_to_long
 #----------------------------------------------------------------------
 # Output
 
-print <<__EOF__;
+if ($mode eq "aclsym") {
+    print <<__EOF__;
+# Automatically generated by gendispatch.pl.
+# Do not edit this file.  Any changes you make will be lost.
+__EOF__
+} else {
+    print <<__EOF__;
 /* Automatically generated by gendispatch.pl.
  * Do not edit this file.  Any changes you make will be lost.
  */
 __EOF__
+}
 
 # Debugging.
 if ($mode eq "debug") {
@@ -1617,4 +1633,190 @@ elsif ($mode eq "client") {
         print "    return rv;\n";
         print "}\n";
     }
+} elsif ($mode eq "aclheader" ||
+         $mode eq "aclbody" ||
+         $mode eq "aclsym") {
+    my %generate = map { $_ => 1 } @autogen;
+    my @keys = keys %calls;
+
+    if ($mode eq "aclsym") {
+        @keys = sort { my $c = $a . "ensureacl";
+                       my $d = $b . "ensureacl";
+                       $c cmp $d } @keys;
+    } else {
+        @keys = sort { $a cmp $b } @keys;
+    }
+
+    if ($mode eq "aclheader") {
+        my @headers = (
+            "internal.h",
+            "domain_conf.h",
+            "network_conf.h",
+            "secret_conf.h",
+            "storage_conf.h",
+            "nwfilter_conf.h",
+            "node_device_conf.h",
+            "interface_conf.h"
+            );
+        foreach my $hdr (@headers) {
+            print "#include \"$hdr\"\n";
+        }
+    } elsif ($mode eq "aclbody") {
+        my $header = shift;
+        print "#include <config.h>\n";
+        print "#include \"$header\"\n";
+        print "#include \"access/viraccessmanager.h\"\n";
+        print "#include \"datatypes.h\"\n";
+        print "#include \"virerror.h\"\n";
+        print "\n";
+        print "#define VIR_FROM_THIS VIR_FROM_ACCESS\n";
+    }
+    print "\n";
+
+    foreach (@keys) {
+        my $call = $calls{$_};
+
+        die "missing 'acl' option for $call->{ProcName}"
+            unless exists $call->{acl} &&
+            $#{$call->{acl}} != -1;
+
+        next if $call->{acl}->[0] eq "none";
+
+        if ($mode eq "aclsym") {
+            my $apiname = "vir" . $call->{ProcName};
+            if ($structprefix eq "qemu") {
+                $apiname =~ s/virDomain/virDomainQemu/;
+            } elsif ($structprefix eq "lxc") {
+                $apiname =~ s/virDomain/virDomainLxc/;
+            }
+            if (defined $call->{aclfilter}) {
+                print $apiname . "CheckACL;\n";
+            }
+            print $apiname . "EnsureACL;\n";
+        } else {
+            &generate_acl($call, $call->{acl}, "Ensure");
+            if (defined $call->{aclfilter}) {
+                &generate_acl($call, $call->{aclfilter}, "Check");
+            }
+        }
+
+        sub generate_acl {
+            my $call = shift;
+            my $acl = shift;
+            my $action = shift;
+
+            my @acl;
+            foreach (@{$acl}) {
+                my @bits = split /:/;
+                push @acl, { object => $bits[0], perm => $bits[1], flags => $bits[2] }
+            }
+
+            my $checkflags = 0;
+            for (my $i = 1 ; $i <= $#acl ; $i++) {
+                if ($acl[$i]->{object} ne $acl[0]->{object}) {
+                    die "acl for '$call->{ProcName}' cannot check different objects";
+                }
+                if (defined $acl[$i]->{flags}) {
+                    $checkflags = 1;
+                }
+            }
+
+            my $apiname = "vir" . $call->{ProcName};
+            if ($structprefix eq "qemu") {
+                $apiname =~ s/virDomain/virDomainQemu/;
+            } elsif ($structprefix eq "lxc") {
+                $apiname =~ s/virDomain/virDomainLxc/;
+            }
+
+            my $object = $acl[0]->{object};
+            my $arg = $acl[0]->{object};
+            $arg =~ s/^.*_(\w+)$/$1/;
+            $object =~ s/^(\w)/uc $1/e;
+            $object =~ s/_(\w)/uc $1/e;
+            $object =~ s/Nwfilter/NWFilter/;
+            my $objecttype = "vir" . $object . "DefPtr";
+            $apiname .= $action . "ACL";
+
+            if ($arg eq "interface") {
+                $arg = "iface";
+            }
+
+            my @argdecls;
+            push @argdecls, "virConnectPtr conn";
+            if ($object ne "Connect") {
+                if ($object eq "StorageVol") {
+                    push @argdecls, "virStoragePoolDefPtr pool";
+                }
+                push @argdecls, "$objecttype $arg";
+            }
+            if ($checkflags) {
+                push @argdecls, "unsigned int flags";
+            }
+
+            if ($mode eq "aclheader") {
+                print "extern int $apiname(" . join(", ", @argdecls) . ");\n";
+            } else {
+                my @argvars;
+                push @argvars, "mgr";
+                push @argvars, "conn->driver->name";
+                if ($object ne "Connect") {
+                    if ($object eq "StorageVol") {
+                        push @argvars, "pool";
+                    }
+                    push @argvars, $arg;
+                }
+
+                if ($action eq "Check") {
+                    print "/* Returns: -1 on error, 0 on denied, 1 on allowed */\n";
+                } else {
+                    print "/* Returns: -1 on error (denied==error), 0 on allowed */\n";
+                }
+                print "int $apiname(" . join(", ", @argdecls) . ")\n";
+                print "{\n";
+                print "    virAccessManagerPtr mgr;\n";
+                print "    int rv;\n";
+                print "\n";
+                print "    if (!(mgr = virAccessManagerGetDefault()))\n";
+                print "        return -1;\n";
+                print "\n";
+
+                foreach my $acl (@acl) {
+                    my $perm = "vir_access_perm_" . $acl->{object} . "_" . $acl->{perm};
+                    $perm =~ tr/a-z/A-Z/;
+
+                    my $method = "virAccessManagerCheck" . $object;
+                    my $space = ' ' x length($method);
+                    print "    if (";
+                    if (defined $acl->{flags}) {
+                        my $flags = $acl->{flags};
+                        if ($flags =~ /^\!/) {
+                            $flags = substr $flags, 1;
+                            print "((flags & ($flags)) == 0) &&\n";
+                        } else {
+                            print "((flags & ($flags)) == ($flags)) &&\n";
+                        }
+                        print "        ";
+                    }
+                    print "(rv = $method(" . join(", ", @argvars, $perm) . ")) <= 0) {\n";
+                    if ($action eq "Ensure") {
+                        print "        if (rv == 0)\n";
+                        print "            virReportError(VIR_ERR_ACCESS_DENIED, NULL);\n";
+                        print "        return -1;\n";
+                    } else {
+                        print "        return rv;\n";
+                    }
+                    print "    }";
+                    print "\n";
+                }
+
+                if ($action eq "Check") {
+                    print "    return 1;\n";
+                } else {
+                    print "    return 0;\n";
+                }
+                print "}\n\n";
+            }
+        }
+    }
 }
+
-- 
1.8.1.4




More information about the libvir-list mailing list