From: "Daniel P. Berrange" <berrange@xxxxxxxxxx> 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@xxxxxxxxxx> --- .gitignore | 9 +++ src/Makefile.am | 55 ++++++++++++- src/rpc/gendispatch.pl | 209 ++++++++++++++++++++++++++++++++++++++++++++++++- 3 files changed, 268 insertions(+), 5 deletions(-) diff --git a/.gitignore b/.gitignore index f9168fc..7a28941 100644 --- a/.gitignore +++ b/.gitignore @@ -104,10 +104,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 a099d7a..945d8e1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -789,6 +789,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 \ @@ -1393,7 +1402,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 = \ @@ -1426,6 +1435,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 4d5007f..1232455 100755 --- a/src/rpc/gendispatch.pl +++ b/src/rpc/gendispatch.pl @@ -40,8 +40,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"; @@ -124,7 +125,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*$,) { @@ -251,6 +258,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) @@ -337,11 +346,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") { @@ -1626,4 +1642,189 @@ 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 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list