qemu with hax to log dma reads & writes jcs.org/2018/11/12/vfio

authz: add QAuthZPAM object type for authorizing using PAM

Add an authorization backend that talks to PAM to check whether the user
identity is allowed. This only uses the PAM account validation facility,
which is essentially just a check to see if the provided username is permitted
access. It doesn't use the authentication or session parts of PAM, since
that's dealt with by the relevant part of QEMU (eg VNC server).

Consider starting QEMU with a VNC server and telling it to use TLS with
x509 client certificates and configuring it to use an PAM to validate
the x509 distinguished name. In this example we're telling it to use PAM
for the QAuthZ impl with a service name of "qemu-vnc"

$ qemu-system-x86_64 \
-object tls-creds-x509,id=tls0,dir=/home/berrange/security/qemutls,\
endpoint=server,verify-peer=yes \
-object authz-pam,id=authz0,service=qemu-vnc \
-vnc :1,tls-creds=tls0,tls-authz=authz0

This requires an /etc/pam/qemu-vnc file to be created with the auth
rules. A very simple file based whitelist can be setup using

$ cat > /etc/pam/qemu-vnc <<EOF
account requisite pam_listfile.so item=user sense=allow file=/etc/qemu/vnc.allow
EOF

The /etc/qemu/vnc.allow file simply contains one username per line. Any
username not in the file is denied. The usernames in this example are
the x509 distinguished name from the client's x509 cert.

$ cat > /etc/qemu/vnc.allow <<EOF
CN=laptop.berrange.com,O=Berrange Home,L=London,ST=London,C=GB
EOF

More interesting would be to configure PAM to use an LDAP backend, so
that the QEMU authorization check data can be centralized instead of
requiring each compute host to have file maintained.

The main limitation with this PAM module is that the rules apply to all
QEMU instances on the host. Setting up different rules per VM, would
require creating a separate PAM service name & config file for every
guest. An alternative approach for the future might be to not pass in
the plain username to PAM, but instead combine the VM name or UUID with
the username. This requires further consideration though.

Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

+452
+3
authz/Makefile.objs
··· 2 2 authz-obj-y += simple.o 3 3 authz-obj-y += list.o 4 4 authz-obj-y += listfile.o 5 + authz-obj-$(CONFIG_AUTH_PAM) += pamacct.o 6 + 7 + pamacct.o-libs = -lpam
+148
authz/pamacct.c
··· 1 + /* 2 + * QEMU PAM authorization driver 3 + * 4 + * Copyright (c) 2018 Red Hat, Inc. 5 + * 6 + * This library is free software; you can redistribute it and/or 7 + * modify it under the terms of the GNU Lesser General Public 8 + * License as published by the Free Software Foundation; either 9 + * version 2 of the License, or (at your option) any later version. 10 + * 11 + * This library is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 + * Lesser General Public License for more details. 15 + * 16 + * You should have received a copy of the GNU Lesser General Public 17 + * License along with this library; if not, see <http://www.gnu.org/licenses/>. 18 + * 19 + */ 20 + 21 + #include "qemu/osdep.h" 22 + #include "authz/pamacct.h" 23 + #include "authz/trace.h" 24 + #include "qom/object_interfaces.h" 25 + 26 + #include <security/pam_appl.h> 27 + 28 + 29 + static bool qauthz_pam_is_allowed(QAuthZ *authz, 30 + const char *identity, 31 + Error **errp) 32 + { 33 + QAuthZPAM *pauthz = QAUTHZ_PAM(authz); 34 + const struct pam_conv pam_conversation = { 0 }; 35 + pam_handle_t *pamh = NULL; 36 + int ret; 37 + 38 + trace_qauthz_pam_check(authz, identity, pauthz->service); 39 + ret = pam_start(pauthz->service, 40 + identity, 41 + &pam_conversation, 42 + &pamh); 43 + if (ret != PAM_SUCCESS) { 44 + error_setg(errp, "Unable to start PAM transaction: %s", 45 + pam_strerror(NULL, ret)); 46 + return false; 47 + } 48 + 49 + ret = pam_acct_mgmt(pamh, PAM_SILENT); 50 + pam_end(pamh, ret); 51 + if (ret != PAM_SUCCESS) { 52 + error_setg(errp, "Unable to authorize user '%s': %s", 53 + identity, pam_strerror(pamh, ret)); 54 + return false; 55 + } 56 + 57 + return true; 58 + } 59 + 60 + 61 + static void 62 + qauthz_pam_prop_set_service(Object *obj, 63 + const char *service, 64 + Error **errp G_GNUC_UNUSED) 65 + { 66 + QAuthZPAM *pauthz = QAUTHZ_PAM(obj); 67 + 68 + g_free(pauthz->service); 69 + pauthz->service = g_strdup(service); 70 + } 71 + 72 + 73 + static char * 74 + qauthz_pam_prop_get_service(Object *obj, 75 + Error **errp G_GNUC_UNUSED) 76 + { 77 + QAuthZPAM *pauthz = QAUTHZ_PAM(obj); 78 + 79 + return g_strdup(pauthz->service); 80 + } 81 + 82 + 83 + static void 84 + qauthz_pam_complete(UserCreatable *uc, Error **errp) 85 + { 86 + } 87 + 88 + 89 + static void 90 + qauthz_pam_finalize(Object *obj) 91 + { 92 + QAuthZPAM *pauthz = QAUTHZ_PAM(obj); 93 + 94 + g_free(pauthz->service); 95 + } 96 + 97 + 98 + static void 99 + qauthz_pam_class_init(ObjectClass *oc, void *data) 100 + { 101 + UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc); 102 + QAuthZClass *authz = QAUTHZ_CLASS(oc); 103 + 104 + ucc->complete = qauthz_pam_complete; 105 + authz->is_allowed = qauthz_pam_is_allowed; 106 + 107 + object_class_property_add_str(oc, "service", 108 + qauthz_pam_prop_get_service, 109 + qauthz_pam_prop_set_service, 110 + NULL); 111 + } 112 + 113 + 114 + QAuthZPAM *qauthz_pam_new(const char *id, 115 + const char *service, 116 + Error **errp) 117 + { 118 + return QAUTHZ_PAM( 119 + object_new_with_props(TYPE_QAUTHZ_PAM, 120 + object_get_objects_root(), 121 + id, errp, 122 + "service", service, 123 + NULL)); 124 + } 125 + 126 + 127 + static const TypeInfo qauthz_pam_info = { 128 + .parent = TYPE_QAUTHZ, 129 + .name = TYPE_QAUTHZ_PAM, 130 + .instance_size = sizeof(QAuthZPAM), 131 + .instance_finalize = qauthz_pam_finalize, 132 + .class_size = sizeof(QAuthZPAMClass), 133 + .class_init = qauthz_pam_class_init, 134 + .interfaces = (InterfaceInfo[]) { 135 + { TYPE_USER_CREATABLE }, 136 + { } 137 + } 138 + }; 139 + 140 + 141 + static void 142 + qauthz_pam_register_types(void) 143 + { 144 + type_register_static(&qauthz_pam_info); 145 + } 146 + 147 + 148 + type_init(qauthz_pam_register_types);
+3
authz/trace-events
··· 13 13 # auth/listfile.c 14 14 qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s" 15 15 qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d" 16 + 17 + # auth/pam.c 18 + qauthz_pam_check(void *authz, const char *identity, const char *service) "AuthZ PAM %p identity=%s service=%s"
+37
configure
··· 463 463 nettle="" 464 464 gcrypt="" 465 465 gcrypt_hmac="no" 466 + auth_pam="" 466 467 vte="" 467 468 virglrenderer="" 468 469 tpm="yes" ··· 1380 1381 --disable-gcrypt) gcrypt="no" 1381 1382 ;; 1382 1383 --enable-gcrypt) gcrypt="yes" 1384 + ;; 1385 + --disable-auth-pam) auth_pam="no" 1386 + ;; 1387 + --enable-auth-pam) auth_pam="yes" 1383 1388 ;; 1384 1389 --enable-rdma) rdma="yes" 1385 1390 ;; ··· 1707 1712 gnutls GNUTLS cryptography support 1708 1713 nettle nettle cryptography support 1709 1714 gcrypt libgcrypt cryptography support 1715 + auth-pam PAM access control 1710 1716 sdl SDL UI 1711 1717 sdl_image SDL Image support for icons 1712 1718 gtk gtk UI ··· 2863 2869 tasn1=no 2864 2870 fi 2865 2871 2872 + 2873 + ########################################## 2874 + # PAM probe 2875 + 2876 + if test "$auth_pam" != "no"; then 2877 + cat > $TMPC <<EOF 2878 + #include <security/pam_appl.h> 2879 + #include <stdio.h> 2880 + int main(void) { 2881 + const char *service_name = "qemu"; 2882 + const char *user = "frank"; 2883 + const struct pam_conv *pam_conv = NULL; 2884 + pam_handle_t *pamh = NULL; 2885 + pam_start(service_name, user, pam_conv, &pamh); 2886 + return 0; 2887 + } 2888 + EOF 2889 + if compile_prog "" "-lpam" ; then 2890 + auth_pam=yes 2891 + else 2892 + if test "$auth_pam" = "yes"; then 2893 + feature_not_found "PAM" "Install PAM development package" 2894 + else 2895 + auth_pam=no 2896 + fi 2897 + fi 2898 + fi 2866 2899 2867 2900 ########################################## 2868 2901 # getifaddrs (for tests/test-io-channel-socket ) ··· 6091 6124 echo "libgcrypt $gcrypt" 6092 6125 echo "nettle $nettle $(echo_version $nettle $nettle_version)" 6093 6126 echo "libtasn1 $tasn1" 6127 + echo "PAM $auth_pam" 6094 6128 echo "curses support $curses" 6095 6129 echo "virgl support $virglrenderer $(echo_version $virglrenderer $virgl_version)" 6096 6130 echo "curl support $curl" ··· 6549 6583 fi 6550 6584 if test "$tasn1" = "yes" ; then 6551 6585 echo "CONFIG_TASN1=y" >> $config_host_mak 6586 + fi 6587 + if test "$auth_pam" = "yes" ; then 6588 + echo "CONFIG_AUTH_PAM=y" >> $config_host_mak 6552 6589 fi 6553 6590 if test "$have_ifaddrs_h" = "yes" ; then 6554 6591 echo "HAVE_IFADDRS_H=y" >> $config_host_mak
+100
include/authz/pamacct.h
··· 1 + /* 2 + * QEMU PAM authorization driver 3 + * 4 + * Copyright (c) 2018 Red Hat, Inc. 5 + * 6 + * This library is free software; you can redistribute it and/or 7 + * modify it under the terms of the GNU Lesser General Public 8 + * License as published by the Free Software Foundation; either 9 + * version 2 of the License, or (at your option) any later version. 10 + * 11 + * This library is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 + * Lesser General Public License for more details. 15 + * 16 + * You should have received a copy of the GNU Lesser General Public 17 + * License along with this library; if not, see <http://www.gnu.org/licenses/>. 18 + * 19 + */ 20 + 21 + #ifndef QAUTHZ_PAM_H__ 22 + #define QAUTHZ_PAM_H__ 23 + 24 + #include "authz/base.h" 25 + 26 + 27 + #define TYPE_QAUTHZ_PAM "authz-pam" 28 + 29 + #define QAUTHZ_PAM_CLASS(klass) \ 30 + OBJECT_CLASS_CHECK(QAuthZPAMClass, (klass), \ 31 + TYPE_QAUTHZ_PAM) 32 + #define QAUTHZ_PAM_GET_CLASS(obj) \ 33 + OBJECT_GET_CLASS(QAuthZPAMClass, (obj), \ 34 + TYPE_QAUTHZ_PAM) 35 + #define QAUTHZ_PAM(obj) \ 36 + INTERFACE_CHECK(QAuthZPAM, (obj), \ 37 + TYPE_QAUTHZ_PAM) 38 + 39 + typedef struct QAuthZPAM QAuthZPAM; 40 + typedef struct QAuthZPAMClass QAuthZPAMClass; 41 + 42 + 43 + /** 44 + * QAuthZPAM: 45 + * 46 + * This authorization driver provides a PAM mechanism 47 + * for granting access by matching user names against a 48 + * list of globs. Each match rule has an associated policy 49 + * and a catch all policy applies if no rule matches 50 + * 51 + * To create an instance of this class via QMP: 52 + * 53 + * { 54 + * "execute": "object-add", 55 + * "arguments": { 56 + * "qom-type": "authz-pam", 57 + * "id": "authz0", 58 + * "parameters": { 59 + * "service": "qemu-vnc-tls" 60 + * } 61 + * } 62 + * } 63 + * 64 + * The driver only uses the PAM "account" verification 65 + * subsystem. The above config would require a config 66 + * file /etc/pam.d/qemu-vnc-tls. For a simple file 67 + * lookup it would contain 68 + * 69 + * account requisite pam_listfile.so item=user sense=allow \ 70 + * file=/etc/qemu/vnc.allow 71 + * 72 + * The external file would then contain a list of usernames. 73 + * If x509 cert was being used as the username, a suitable 74 + * entry would match the distinguish name: 75 + * 76 + * CN=laptop.berrange.com,O=Berrange Home,L=London,ST=London,C=GB 77 + * 78 + * On the command line it can be created using 79 + * 80 + * -object authz-pam,id=authz0,service=qemu-vnc-tls 81 + * 82 + */ 83 + struct QAuthZPAM { 84 + QAuthZ parent_obj; 85 + 86 + char *service; 87 + }; 88 + 89 + 90 + struct QAuthZPAMClass { 91 + QAuthZClass parent_class; 92 + }; 93 + 94 + 95 + QAuthZPAM *qauthz_pam_new(const char *id, 96 + const char *service, 97 + Error **errp); 98 + 99 + 100 + #endif /* QAUTHZ_PAM_H__ */
+35
qemu-options.hx
··· 4435 4435 ... 4436 4436 @end example 4437 4437 4438 + @item -object authz-pam,id=@var{id},service=@var{string} 4439 + 4440 + Create an authorization object that will control access to network services. 4441 + 4442 + The @option{service} parameter provides the name of a PAM service to use 4443 + for authorization. It requires that a file @code{/etc/pam.d/@var{service}} 4444 + exist to provide the configuration for the @code{account} subsystem. 4445 + 4446 + An example authorization object to validate a TLS x509 distinguished 4447 + name would look like: 4448 + 4449 + @example 4450 + # $QEMU \ 4451 + ... 4452 + -object authz-pam,id=auth0,service=qemu-vnc 4453 + ... 4454 + @end example 4455 + 4456 + There would then be a corresponding config file for PAM at 4457 + @code{/etc/pam.d/qemu-vnc} that contains: 4458 + 4459 + @example 4460 + account requisite pam_listfile.so item=user sense=allow \ 4461 + file=/etc/qemu/vnc.allow 4462 + @end example 4463 + 4464 + Finally the @code{/etc/qemu/vnc.allow} file would contain 4465 + the list of x509 distingished names that are permitted 4466 + access 4467 + 4468 + @example 4469 + CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB 4470 + @end example 4471 + 4472 + 4438 4473 @end table 4439 4474 4440 4475 ETEXI
+2
tests/Makefile.include
··· 119 119 check-unit-y += tests/test-authz-simple$(EXESUF) 120 120 check-unit-y += tests/test-authz-list$(EXESUF) 121 121 check-unit-y += tests/test-authz-listfile$(EXESUF) 122 + check-unit-$(CONFIG_AUTH_PAM) += tests/test-authz-pam$(EXESUF) 122 123 check-unit-y += tests/test-io-task$(EXESUF) 123 124 check-unit-y += tests/test-io-channel-socket$(EXESUF) 124 125 check-unit-y += tests/test-io-channel-file$(EXESUF) ··· 669 670 tests/test-authz-simple$(EXESUF): tests/test-authz-simple.o $(test-authz-obj-y) 670 671 tests/test-authz-list$(EXESUF): tests/test-authz-list.o $(test-authz-obj-y) 671 672 tests/test-authz-listfile$(EXESUF): tests/test-authz-listfile.o $(test-authz-obj-y) 673 + tests/test-authz-pam$(EXESUF): tests/test-authz-pam.o $(test-authz-obj-y) 672 674 tests/test-io-task$(EXESUF): tests/test-io-task.o $(test-io-obj-y) 673 675 tests/test-io-channel-socket$(EXESUF): tests/test-io-channel-socket.o \ 674 676 tests/io-channel-helpers.o tests/socket-helpers.o $(test-io-obj-y)
+124
tests/test-authz-pam.c
··· 1 + /* 2 + * QEMU PAM authorization object tests 3 + * 4 + * Copyright (c) 2018 Red Hat, Inc. 5 + * 6 + * This library is free software; you can redistribute it and/or 7 + * modify it under the terms of the GNU Lesser General Public 8 + * License as published by the Free Software Foundation; either 9 + * version 2 of the License, or (at your option) any later version. 10 + * 11 + * This library is distributed in the hope that it will be useful, 12 + * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14 + * Lesser General Public License for more details. 15 + * 16 + * You should have received a copy of the GNU Lesser General Public 17 + * License along with this library; if not, see <http://www.gnu.org/licenses/>. 18 + * 19 + */ 20 + 21 + #include "qemu/osdep.h" 22 + #include "qapi/error.h" 23 + #include "authz/pamacct.h" 24 + 25 + #include <security/pam_appl.h> 26 + 27 + static bool failauth; 28 + 29 + /* 30 + * These two functions are exported by libpam.so. 31 + * 32 + * By defining them again here, our impls are resolved 33 + * by the linker instead of those in libpam.so 34 + * 35 + * The test suite is thus isolated from the host system 36 + * PAM setup, so we can do predictable test scenarios 37 + */ 38 + int 39 + pam_start(const char *service_name, const char *user, 40 + const struct pam_conv *pam_conversation, 41 + pam_handle_t **pamh) 42 + { 43 + failauth = true; 44 + if (!g_str_equal(service_name, "qemu-vnc")) { 45 + return PAM_AUTH_ERR; 46 + } 47 + 48 + if (g_str_equal(user, "fred")) { 49 + failauth = false; 50 + } 51 + 52 + return PAM_SUCCESS; 53 + } 54 + 55 + 56 + int 57 + pam_acct_mgmt(pam_handle_t *pamh, int flags) 58 + { 59 + if (failauth) { 60 + return PAM_AUTH_ERR; 61 + } 62 + 63 + return PAM_SUCCESS; 64 + } 65 + 66 + 67 + static void test_authz_unknown_service(void) 68 + { 69 + Error *local_err = NULL; 70 + QAuthZPAM *auth = qauthz_pam_new("auth0", 71 + "qemu-does-not-exist", 72 + &error_abort); 73 + 74 + g_assert_nonnull(auth); 75 + 76 + g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "fred", &local_err)); 77 + 78 + error_free_or_abort(&local_err); 79 + object_unparent(OBJECT(auth)); 80 + } 81 + 82 + 83 + static void test_authz_good_user(void) 84 + { 85 + QAuthZPAM *auth = qauthz_pam_new("auth0", 86 + "qemu-vnc", 87 + &error_abort); 88 + 89 + g_assert_nonnull(auth); 90 + 91 + g_assert_true(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort)); 92 + 93 + object_unparent(OBJECT(auth)); 94 + } 95 + 96 + 97 + static void test_authz_bad_user(void) 98 + { 99 + Error *local_err = NULL; 100 + QAuthZPAM *auth = qauthz_pam_new("auth0", 101 + "qemu-vnc", 102 + &error_abort); 103 + 104 + g_assert_nonnull(auth); 105 + 106 + g_assert_false(qauthz_is_allowed(QAUTHZ(auth), "bob", &local_err)); 107 + 108 + error_free_or_abort(&local_err); 109 + object_unparent(OBJECT(auth)); 110 + } 111 + 112 + 113 + int main(int argc, char **argv) 114 + { 115 + g_test_init(&argc, &argv, NULL); 116 + 117 + module_call_init(MODULE_INIT_QOM); 118 + 119 + g_test_add_func("/auth/pam/unknown-service", test_authz_unknown_service); 120 + g_test_add_func("/auth/pam/good-user", test_authz_good_user); 121 + g_test_add_func("/auth/pam/bad-user", test_authz_bad_user); 122 + 123 + return g_test_run(); 124 + }