All of lore.kernel.org
 help / color / mirror / Atom feed
* [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework
@ 2018-10-19 13:38 Daniel P. Berrangé
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner Daniel P. Berrangé
                   ` (10 more replies)
  0 siblings, 11 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

An update to

 v2: https://lists.gnu.org/archive/html/qemu-devel/2018-06/msg04469.html
 v3: https://lists.gnu.org/archive/html/qemu-devel/2018-06/msg05660.html
 v4: https://lists.gnu.org/archive/html/qemu-devel/2018-08/msg02961.html
 v5: https://lists.gnu.org/archive/html/qemu-devel/2018-10/msg01613.html

The current network services now support encryption via TLS and in some
cases support authentication via SASL. In cases where SASL is not
available, x509 client certificates can be used as a crude authorization
scheme, but using a sub-CA and controlling who you give certs to. In
general this is not very flexible though, so this series introduces a
new standard authorization framework.

It comes with four initial authorization mechanisms

 - Simple - an exact username match. This is useful when there is
   exactly one user that is known to connect. For example when live
   migrating from one QEMU to another with TLS, libvirt would use
   the simple scheme to whitelist the TLS cert of the source QEMU.

 - List - an full access control list, with optional regex matching.
   This is more flexible and is used to provide 100% backcompat with
   the existing HMP ACL commands. The caveat is that we can't create
   these via the CLI -object arg yet.

 - ListFile - the same as List, but with the rules stored in JSON
   format in an external file. This avoids the -object limitation
   while also allowing the admin to change list entries on the file.
   QEMU uses inotify to notice these changes and auto-reload the
   file contents. This is likely a good default choice for most
   network services, if the "simple" mechanism isn't sufficient.

 - PAM - delegate the username lookup to a PAM module, which opens
   the door to many options including things like SQL/LDAP lookups.

A later series that follows will integrate this framework into the VNC,
NBD, migration, and character device servers.

Changed in v6:

 - Fix consistency of object variable names
 - Fix command line example quoting
 - Use 'false' not '0' for bool return values
 - Remove empty 'complete' methods
 - Fix leak of rules in finalizer
 - Fix missing {} around conditional
 - Refactor rule refresh to use goto & invert conditional

Changed in v5:

 - Rebase to latest git master

Changed in v4:

 - Rebase to latest git master

Changed in v3:

 - Added docs for object types in qemu-options.hx
 - Added example CLI syntax in header files
 - Improved commit messages

Changed in v2:

 - Switch to a global shared instance of the file monitor so only
   a single inotify file descriptor is required

 - Require all watches to be registered against directories. File
   watches are useless in Linux, since they are tied to inodes, and
   so stop working when editors save by doing a tmpfile + rename
   dance.

 - Change auth list impl to use a directory based watch instead
   of filename

 - Put MTP const-ness fixes in separate patch

 - Split QOM change off into separate patch

 - Fix conditionals on Win32 build

Daniel P. Berrangé (11):
  util: add helper APIs for dealing with inotify in portable manner
  qom: don't require user creatable objects to be registered
  hw/usb: don't set IN_ISDIR for inotify watch in MTP driver
  hw/usb: fix const-ness for string params in MTP driver
  hw/usb: switch MTP to use new inotify APIs
  authz: add QAuthZ object as an authorization base class
  authz: add QAuthZSimple object type for easy whitelist auth checks
  authz: add QAuthZList object type for an access control list
  authz: add QAuthZListFile object type for a file access control list
  authz: add QAuthZPAM object type for authorizing using PAM
  authz: delete existing ACL implementation

 configure                      |  37 ++++
 Makefile                       |  17 +-
 Makefile.objs                  |  10 ++
 Makefile.target                |   2 +
 qapi/authz.json                |  58 ++++++
 qapi/qapi-schema.json          |   1 +
 include/authz/base.h           | 112 ++++++++++++
 include/authz/list.h           | 106 +++++++++++
 include/authz/listfile.h       | 110 ++++++++++++
 include/authz/pamacct.h        | 100 +++++++++++
 include/authz/simple.h         |  84 +++++++++
 include/qemu/acl.h             |  66 -------
 include/qemu/filemonitor.h     | 117 ++++++++++++
 ui/vnc-auth-sasl.h             |   5 +-
 ui/vnc.h                       |   4 +-
 authz/base.c                   |  82 +++++++++
 authz/list.c                   | 309 ++++++++++++++++++++++++++++++++
 authz/listfile.c               | 286 ++++++++++++++++++++++++++++++
 authz/pamacct.c                | 149 ++++++++++++++++
 authz/simple.c                 | 115 ++++++++++++
 crypto/tlssession.c            |  35 ++--
 hw/usb/dev-mtp.c               | 257 ++++++++++-----------------
 monitor.c                      | 185 ++++++++++++-------
 qom/object.c                   |  12 +-
 qom/object_interfaces.c        |  16 +-
 tests/test-authz-list.c        | 171 ++++++++++++++++++
 tests/test-crypto-tlssession.c |  15 +-
 tests/test-io-channel-tls.c    |  16 +-
 ui/vnc-auth-sasl.c             |  23 ++-
 ui/vnc-auth-vencrypt.c         |   2 +-
 ui/vnc-ws.c                    |   2 +-
 ui/vnc.c                       |  37 ++--
 util/acl.c                     | 179 -------------------
 util/filemonitor.c             | 315 +++++++++++++++++++++++++++++++++
 .gitignore                     |   4 +
 MAINTAINERS                    |  14 ++
 authz/Makefile.objs            |   7 +
 authz/trace-events             |  18 ++
 crypto/trace-events            |   2 +-
 hw/usb/trace-events            |   2 +-
 qemu-options.hx                | 105 +++++++++++
 tests/Makefile.include         |   8 +-
 util/Makefile.objs             |   2 +-
 util/trace-events              |   9 +
 44 files changed, 2667 insertions(+), 539 deletions(-)
 create mode 100644 qapi/authz.json
 create mode 100644 include/authz/base.h
 create mode 100644 include/authz/list.h
 create mode 100644 include/authz/listfile.h
 create mode 100644 include/authz/pamacct.h
 create mode 100644 include/authz/simple.h
 delete mode 100644 include/qemu/acl.h
 create mode 100644 include/qemu/filemonitor.h
 create mode 100644 authz/base.c
 create mode 100644 authz/list.c
 create mode 100644 authz/listfile.c
 create mode 100644 authz/pamacct.c
 create mode 100644 authz/simple.c
 create mode 100644 tests/test-authz-list.c
 delete mode 100644 util/acl.c
 create mode 100644 util/filemonitor.c
 create mode 100644 authz/Makefile.objs
 create mode 100644 authz/trace-events

-- 
2.17.2

^ permalink raw reply	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 18:08   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered Daniel P. Berrangé
                   ` (9 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

The inotify userspace API for reading events is quite horrible, so it is
useful to wrap it in a more friendly API to avoid duplicating code
across many users in QEMU. Wrapping it also allows introduction of a
platform portability layer, so that we can add impls for non-Linux based
equivalents in future.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/qemu/filemonitor.h | 117 ++++++++++++++
 util/filemonitor.c         | 315 +++++++++++++++++++++++++++++++++++++
 MAINTAINERS                |   6 +
 util/Makefile.objs         |   1 +
 util/trace-events          |   9 ++
 5 files changed, 448 insertions(+)
 create mode 100644 include/qemu/filemonitor.h
 create mode 100644 util/filemonitor.c

diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
new file mode 100644
index 0000000000..1326272f0a
--- /dev/null
+++ b/include/qemu/filemonitor.h
@@ -0,0 +1,117 @@
+/*
+ * QEMU file monitor helper
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QEMU_FILE_MONITOR_H
+#define QEMU_FILE_MONITOR_H
+
+#include "qemu-common.h"
+
+
+typedef struct QFileMonitor QFileMonitor;
+
+typedef enum {
+    /* File has been created in a dir */
+    QFILE_MONITOR_EVENT_CREATED,
+    /* File has been modified in a dir */
+    QFILE_MONITOR_EVENT_MODIFIED,
+    /* File has been deleted in a dir */
+    QFILE_MONITOR_EVENT_DELETED,
+    /* Dir is no longer being monitored (due to deletion) */
+    QFILE_MONITOR_EVENT_IGNORED,
+} QFileMonitorEvent;
+
+
+/**
+ * QFileMonitorHandler:
+ * @id: id from qemu_file_monitor_add_watch()
+ * @event: the file change that occurred
+ * @filename: the name of the file affected
+ * @opaque: opaque data provided to qemu_file_monitor_add_watch()
+ *
+ * Invoked whenever a file changes. If @event is
+ * QFILE_MONITOR_EVENT_IGNORED, @filename will be
+ * empty.
+ *
+ */
+typedef void (*QFileMonitorHandler)(int id,
+                                    QFileMonitorEvent event,
+                                    const char *filename,
+                                    void *opaque);
+
+/**
+ * qemu_file_monitor_get_instance:
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Acquire a handle to the shared file monitoring object.
+ *
+ * This object does locking internally to enable it to be
+ * safe to use from multiple threads
+ *
+ * If the platform does not support file monitoring, an
+ * error will be reported. Likewise if file monitoring
+ * is supported, but cannot be initialized
+ *
+ * Currently this is implemented on Linux platforms with
+ * the inotify subsystem.
+ *
+ * Returns: the shared monitoring object, or NULL on error
+ */
+QFileMonitor *qemu_file_monitor_get_instance(Error **errp);
+
+/**
+ * qemu_file_monitor_add_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to watch
+ * @filename: optional filename to filter on
+ * @cb: the function to invoke when @dirpath has changes
+ * @opaque: data to pass to @cb
+ * @errp: pointer to a NULL-initialized error object
+ *
+ * Register to receive notifications of changes
+ * in the directory @dirpath. All files in the
+ * directory will be monitored. If the caller is
+ * only interested in one specific file, @filename
+ * can be used to filter events.
+ *
+ * Returns: a positive integer watch ID, or -1 on error
+ */
+int qemu_file_monitor_add_watch(QFileMonitor *mon,
+                                const char *dirpath,
+                                const char *filename,
+                                QFileMonitorHandler cb,
+                                void *opaque,
+                                Error **errp);
+
+/**
+ * qemu_file_monitor_remove_watch:
+ * @mon: the file monitor context
+ * @dirpath: the directory whose contents to unwatch
+ * @id: id of the watch to remove
+ *
+ * Removes the file monitoring watch @id, associated
+ * with the directory @dirpath. This must never be
+ * called from a QFileMonitorHandler callback, or a
+ * deadlock will result.
+ */
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+                                    const char *dirpath,
+                                    int id);
+
+#endif /* QEMU_FILE_MONITOR_H */
diff --git a/util/filemonitor.c b/util/filemonitor.c
new file mode 100644
index 0000000000..67d7aedbe0
--- /dev/null
+++ b/util/filemonitor.c
@@ -0,0 +1,315 @@
+/*
+ * QEMU file_monitor helper
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "qemu/filemonitor.h"
+#include "qemu/main-loop.h"
+#include "qemu/error-report.h"
+#include "qapi/error.h"
+#include "trace.h"
+
+struct QFileMonitor {
+    QemuMutex lock;
+    int fd;
+
+    GHashTable *dirs; /* dirname => QFileMonitorDir */
+    GHashTable *idmap; /* inotify ID => dirname */
+};
+
+
+typedef struct {
+    int id; /* watch ID */
+    char *filename; /* optional filter */
+    QFileMonitorHandler cb;
+    void *opaque;
+} QFileMonitorWatch;
+
+
+typedef struct {
+    char *path;
+    int id; /* inotify ID */
+    int nextid; /* watch ID counter */
+    gsize nwatches;
+    QFileMonitorWatch *watches;
+} QFileMonitorDir;
+
+
+#ifdef CONFIG_INOTIFY1
+#include <sys/inotify.h>
+
+static void qemu_file_monitor_watch(void *arg)
+{
+    QFileMonitor *mon = arg;
+    char buf[4096]
+        __attribute__ ((aligned(__alignof__(struct inotify_event))));
+    int used = 0;
+    int len = read(mon->fd, buf, sizeof(buf));
+
+    qemu_mutex_lock(&mon->lock);
+
+    if (len < 0) {
+        if (errno != EAGAIN) {
+            error_report("Failure monitoring inotify FD, disabling events");
+            goto cleanup;
+        }
+
+        /* no more events right now */
+        goto cleanup;
+    }
+
+    /* Loop over all events in the buffer */
+    while (used < len) {
+        struct inotify_event *ev =
+            (struct inotify_event *)(buf + used);
+        const char *name = ev->len ? ev->name : "";
+        QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
+                                                   GINT_TO_POINTER(ev->wd));
+        uint32_t iev = ev->mask &
+            (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
+             IN_MOVED_TO | IN_MOVED_FROM);
+        int qev;
+        gsize i;
+
+        used += sizeof(struct inotify_event) + ev->len;
+
+        if (!dir) {
+            continue;
+        }
+
+        /*
+         * During a rename operation, the old name gets
+         * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
+         * To simplify life for callers, we turn these into
+         * DELETED and CREATED events
+         */
+        switch (iev) {
+        case IN_CREATE:
+        case IN_MOVED_TO:
+            qev = QFILE_MONITOR_EVENT_CREATED;
+            break;
+        case IN_MODIFY:
+            qev = QFILE_MONITOR_EVENT_MODIFIED;
+            break;
+        case IN_DELETE:
+        case IN_MOVED_FROM:
+            qev = QFILE_MONITOR_EVENT_DELETED;
+            break;
+        case IN_IGNORED:
+            qev = QFILE_MONITOR_EVENT_IGNORED;
+            break;
+        default:
+            g_assert_not_reached();
+        }
+
+        trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
+        for (i = 0; i < dir->nwatches; i++) {
+            QFileMonitorWatch *watch = &dir->watches[i];
+
+            if (watch->filename == NULL ||
+                (name && g_str_equal(watch->filename, name))) {
+                trace_qemu_file_monitor_dispatch(mon, dir->path, name,
+                                                 qev, watch->cb,
+                                                 watch->opaque, watch->id);
+                watch->cb(watch->id, qev, name, watch->opaque);
+            }
+        }
+    }
+
+ cleanup:
+    qemu_mutex_unlock(&mon->lock);
+}
+
+static void
+qemu_file_monitor_dir_free(void *data)
+{
+    QFileMonitorDir *dir = data;
+
+    g_free(dir->watches);
+    g_free(dir);
+}
+
+#endif
+
+static QFileMonitor *
+qemu_file_monitor_new(Error **errp)
+{
+#ifdef CONFIG_INOTIFY1
+    int fd;
+    QFileMonitor *mon;
+
+    fd = inotify_init1(IN_NONBLOCK);
+    if (fd < 0) {
+        error_setg_errno(errp, errno,
+                         "Unable to initialize inotify");
+        return NULL;
+    }
+
+    mon = g_new0(QFileMonitor, 1);
+    qemu_mutex_init(&mon->lock);
+    mon->fd = fd;
+
+    mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
+                                      qemu_file_monitor_dir_free);
+    mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
+
+    trace_qemu_file_monitor_new(mon, mon->fd);
+
+    return mon;
+#else
+    error_setg(errp, "File monitoring not available on this platform");
+    return NULL;
+#endif
+}
+
+
+QFileMonitor *qemu_file_monitor_get_instance(Error **errp)
+{
+    static QFileMonitor *global;
+
+    if (!global) {
+        global = qemu_file_monitor_new(errp);
+    }
+
+    return global;
+}
+
+
+#ifdef CONFIG_INOTIFY1
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+                            const char *dirpath,
+                            const char *filename,
+                            QFileMonitorHandler cb,
+                            void *opaque,
+                            Error **errp)
+{
+    QFileMonitorDir *dir;
+    int ret = -1;
+
+    qemu_mutex_lock(&mon->lock);
+    dir = g_hash_table_lookup(mon->dirs, dirpath);
+    if (!dir) {
+        int rv = inotify_add_watch(mon->fd, dirpath,
+                                   IN_CREATE | IN_DELETE | IN_MODIFY |
+                                   IN_MOVED_TO | IN_MOVED_FROM);
+
+        if (rv < 0) {
+            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
+            goto cleanup;
+        }
+
+        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
+
+        dir = g_new0(QFileMonitorDir, 1);
+        dir->path = g_strdup(dirpath);
+        dir->id = rv;
+
+        g_hash_table_insert(mon->dirs, dir->path, dir);
+        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
+
+        if (g_hash_table_size(mon->dirs) == 1) {
+            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
+        }
+    }
+
+    dir->watches = g_renew(QFileMonitorWatch, dir->watches, dir->nwatches + 1);
+
+    dir->watches[dir->nwatches].id = ++dir->nextid;
+    dir->watches[dir->nwatches].filename = filename ? g_strdup(filename) : NULL;
+    dir->watches[dir->nwatches].cb = cb;
+    dir->watches[dir->nwatches].opaque = opaque;
+    dir->nwatches++;
+
+    trace_qemu_file_monitor_add_watch(mon, dirpath,
+                                      filename ? filename : "<none>",
+                                      cb, opaque,
+                                      dir->watches[dir->nwatches - 1].id);
+
+    ret = 0;
+
+ cleanup:
+    qemu_mutex_unlock(&mon->lock);
+    return ret;
+}
+
+
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+                                    const char *dirpath,
+                                    int id)
+{
+    QFileMonitorDir *dir;
+    gsize i;
+
+    qemu_mutex_lock(&mon->lock);
+
+    trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
+
+    dir = g_hash_table_lookup(mon->dirs, dirpath);
+    if (!dir) {
+        goto cleanup;
+    }
+
+    for (i = 0; i < dir->nwatches; i++) {
+        if (dir->watches[i].id == id) {
+            if (i < (dir->nwatches - 1)) {
+                memmove(dir->watches + i,
+                        dir->watches + i + 1,
+                        sizeof(QFileMonitorWatch) *
+                        (dir->nwatches - (i + 1)));
+                dir->watches = g_renew(QFileMonitorWatch, dir->watches,
+                                       dir->nwatches - 1);
+                dir->nwatches--;
+            }
+            break;
+        }
+    }
+
+    if (dir->nwatches == 0) {
+        inotify_rm_watch(mon->fd, dir->id);
+        trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
+
+        g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
+        g_hash_table_remove(mon->dirs, dir->path);
+    }
+
+ cleanup:
+    qemu_mutex_lock(&mon->lock);
+}
+
+#else
+int
+qemu_file_monitor_add_watch(QFileMonitor *mon,
+                            const char *dirpath,
+                            const char *filename,
+                            QFileMonitorHandler cb,
+                            void *opaque,
+                            Error **errp)
+{
+    error_setg(errp, "File monitoring not available on this platform");
+    return -1;
+}
+
+void qemu_file_monitor_remove_watch(QFileMonitor *mon,
+                                    const char *dirpath,
+                                    int id)
+{
+}
+#endif
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 40672c4eba..29bbcf8c25 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1866,6 +1866,12 @@ F: include/qemu/sockets.h
 F: util/qemu-sockets.c
 F: qapi/sockets.json
 
+File monitor
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Odd fixes
+F: util/filemonitor.c
+F: include/qemu/filemonitor.h
+
 Throttling infrastructure
 M: Alberto Garcia <berto@igalia.com>
 S: Supported
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 0820923c18..4d7675d6e7 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -50,5 +50,6 @@ util-obj-y += range.o
 util-obj-y += stats64.o
 util-obj-y += systemd.o
 util-obj-y += iova-tree.o
+util-obj-y += filemonitor.o
 util-obj-$(CONFIG_LINUX) += vfio-helpers.o
 util-obj-$(CONFIG_OPENGL) += drm.o
diff --git a/util/trace-events b/util/trace-events
index 79569b7fdf..ff19b253e2 100644
--- a/util/trace-events
+++ b/util/trace-events
@@ -21,6 +21,15 @@ buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
 buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
 buffer_free(const char *buf, size_t len) "%s: capacity %zd"
 
+# util/filemonitor.c
+qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%u"
+qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int id) "File monitor %p remove watch dir='%s' id=%u"
+qemu_file_monitor_new(void *mon, int fd) "File monitor %p created fd=%d"
+qemu_file_monitor_enable_watch(void *mon, const char *dirpath, int id) "File monitor %p enable watch dir='%s' id=%u"
+qemu_file_monitor_disable_watch(void *mon, const char *dirpath, int id) "Fle monitor %p disable watch dir='%s' id=%u"
+qemu_file_monitor_event(void *mon, const char *dirpath, const char *filename, int mask, unsigned int id) "File monitor %p event dir='%s' file='%s' mask=0x%x id=%u"
+qemu_file_monitor_dispatch(void *mon, const char *dirpath, const char *filename, int ev, void *cb, void *opaque, unsigned int id) "File monitor %p dispatch dir='%s' file='%s' ev=%d cb=%p opaque=%p id=%u"
+
 # util/qemu-coroutine.c
 qemu_aio_coroutine_enter(void *ctx, void *from, void *to, void *opaque) "ctx %p from %p to %p opaque %p"
 qemu_coroutine_yield(void *from, void *to) "from %p to %p"
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 18:09   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver Daniel P. Berrangé
                   ` (8 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

When an object is in turn owned by another user object, it is not
desirable to expose this in the QOM object hierarchy, as it is
just an internal implementation detail, we should be free to change
without exposure.

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 qom/object.c            | 12 ++++++++----
 qom/object_interfaces.c | 16 ++++++++++------
 2 files changed, 18 insertions(+), 10 deletions(-)

diff --git a/qom/object.c b/qom/object.c
index 547dcf97c3..f20f0c45a7 100644
--- a/qom/object.c
+++ b/qom/object.c
@@ -607,15 +607,19 @@ Object *object_new_with_propv(const char *typename,
         goto error;
     }
 
-    object_property_add_child(parent, id, obj, &local_err);
-    if (local_err) {
-        goto error;
+    if (id != NULL) {
+        object_property_add_child(parent, id, obj, &local_err);
+        if (local_err) {
+            goto error;
+        }
     }
 
     if (object_dynamic_cast(obj, TYPE_USER_CREATABLE)) {
         user_creatable_complete(obj, &local_err);
         if (local_err) {
-            object_unparent(obj);
+            if (id != NULL) {
+                object_unparent(obj);
+            }
             goto error;
         }
     }
diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
index 941fd63afd..94d5f91d69 100644
--- a/qom/object_interfaces.c
+++ b/qom/object_interfaces.c
@@ -83,16 +83,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
         goto out;
     }
 
-    object_property_add_child(object_get_objects_root(),
-                              id, obj, &local_err);
-    if (local_err) {
-        goto out;
+    if (id != NULL) {
+        object_property_add_child(object_get_objects_root(),
+                                  id, obj, &local_err);
+        if (local_err) {
+            goto out;
+        }
     }
 
     user_creatable_complete(obj, &local_err);
     if (local_err) {
-        object_property_del(object_get_objects_root(),
-                            id, &error_abort);
+        if (id != NULL) {
+            object_property_del(object_get_objects_root(),
+                                id, &error_abort);
+        }
         goto out;
     }
 out:
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner Daniel P. Berrangé
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 18:10   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params " Daniel P. Berrangé
                   ` (7 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

IN_ISDIR is not a bit that one can request when registering a
watch with inotify_add_watch. Rather it is a bit that is set
automatically when reading events from the kernel.

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 hw/usb/dev-mtp.c | 3 +--
 1 file changed, 1 insertion(+), 2 deletions(-)

diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
index 00a3691bae..f026419e47 100644
--- a/hw/usb/dev-mtp.c
+++ b/hw/usb/dev-mtp.c
@@ -642,8 +642,7 @@ static void usb_mtp_inotify_cleanup(MTPState *s)
 
 static int usb_mtp_add_watch(int inotifyfd, char *path)
 {
-    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY |
-        IN_ISDIR;
+    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
 
     return inotify_add_watch(inotifyfd, path, mask);
 }
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params in MTP driver
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (2 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 18:11   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs Daniel P. Berrangé
                   ` (6 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

Various functions accepting 'char *' string parameters were missing
'const' qualifiers.

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 hw/usb/dev-mtp.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)

diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
index f026419e47..ccbe25820b 100644
--- a/hw/usb/dev-mtp.c
+++ b/hw/usb/dev-mtp.c
@@ -372,7 +372,7 @@ static const USBDesc desc = {
 /* ----------------------------------------------------------------------- */
 
 static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
-                                       MTPObject *parent, char *name)
+                                       MTPObject *parent, const char *name)
 {
     MTPObject *o = g_new0(MTPObject, 1);
 
@@ -454,7 +454,7 @@ static MTPObject *usb_mtp_object_lookup(MTPState *s, uint32_t handle)
 }
 
 static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
-                                    char *name)
+                                    const char *name)
 {
     MTPObject *child =
         usb_mtp_object_alloc(s, s->next_handle++, o, name);
@@ -473,7 +473,7 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
 }
 
 static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
-                                             char *name, int len)
+                                             const char *name, int len)
 {
     MTPObject *iter;
 
@@ -640,7 +640,7 @@ static void usb_mtp_inotify_cleanup(MTPState *s)
     }
 }
 
-static int usb_mtp_add_watch(int inotifyfd, char *path)
+static int usb_mtp_add_watch(int inotifyfd, const char *path)
 {
     uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
 
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (3 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params " Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 18:26   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class Daniel P. Berrangé
                   ` (5 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

The internal inotify APIs allow alot of conditional statements to be
cleared out, and provide a simpler callback for handling events.

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 hw/usb/dev-mtp.c    | 250 ++++++++++++++++----------------------------
 hw/usb/trace-events |   2 +-
 2 files changed, 93 insertions(+), 159 deletions(-)

diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
index ccbe25820b..1a8d0f088d 100644
--- a/hw/usb/dev-mtp.c
+++ b/hw/usb/dev-mtp.c
@@ -15,13 +15,11 @@
 #include <dirent.h>
 
 #include <sys/statvfs.h>
-#ifdef CONFIG_INOTIFY1
-#include <sys/inotify.h>
-#include "qemu/main-loop.h"
-#endif
+
 
 #include "qemu-common.h"
 #include "qemu/iov.h"
+#include "qemu/filemonitor.h"
 #include "trace.h"
 #include "hw/usb.h"
 #include "desc.h"
@@ -124,7 +122,6 @@ enum {
     EP_EVENT,
 };
 
-#ifdef CONFIG_INOTIFY1
 typedef struct MTPMonEntry MTPMonEntry;
 
 struct MTPMonEntry {
@@ -133,7 +130,6 @@ struct MTPMonEntry {
 
     QTAILQ_ENTRY(MTPMonEntry) next;
 };
-#endif
 
 struct MTPControl {
     uint16_t     code;
@@ -162,10 +158,8 @@ struct MTPObject {
     char         *name;
     char         *path;
     struct stat  stat;
-#ifdef CONFIG_INOTIFY1
-    /* inotify watch cookie */
+    /* file monitor watch cookie */
     int          watchfd;
-#endif
     MTPObject    *parent;
     uint32_t     nchildren;
     QLIST_HEAD(, MTPObject) children;
@@ -188,11 +182,8 @@ struct MTPState {
     bool         readonly;
 
     QTAILQ_HEAD(, MTPObject) objects;
-#ifdef CONFIG_INOTIFY1
-    /* inotify descriptor */
-    int          inotifyfd;
+    QFileMonitor *file_monitor;
     QTAILQ_HEAD(events, MTPMonEntry) events;
-#endif
     /* Responder is expecting a write operation */
     bool write_pending;
     struct {
@@ -477,6 +468,10 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
 {
     MTPObject *iter;
 
+    if (len == -1) {
+        len = strlen(name);
+    }
+
     QLIST_FOREACH(iter, &parent->children, list) {
         if (strncmp(iter->name, name, len) == 0) {
             return iter;
@@ -486,7 +481,6 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
     return NULL;
 }
 
-#ifdef CONFIG_INOTIFY1
 static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
 {
     MTPObject *iter;
@@ -500,158 +494,98 @@ static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
     return NULL;
 }
 
-static void inotify_watchfn(void *arg)
+static void file_monitor_event(int wd,
+                               QFileMonitorEvent ev,
+                               const char *name,
+                               void *opaque)
 {
-    MTPState *s = arg;
-    ssize_t bytes;
-    /* From the man page: atleast one event can be read */
-    int pos;
-    char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
-
-    for (;;) {
-        bytes = read(s->inotifyfd, buf, sizeof(buf));
-        pos = 0;
-
-        if (bytes <= 0) {
-            /* Better luck next time */
+    MTPState *s = opaque;
+    int watchfd = 0;
+    MTPObject *parent = usb_mtp_object_lookup_wd(s, wd);
+    MTPMonEntry *entry = NULL;
+    MTPObject *o;
+
+    if (!parent) {
+        return;
+    }
+
+    switch (ev) {
+    case QFILE_MONITOR_EVENT_CREATED:
+        if (usb_mtp_object_lookup_name(parent, name, -1)) {
+            /* Duplicate create event */
             return;
         }
+        entry = g_new0(MTPMonEntry, 1);
+        entry->handle = s->next_handle;
+        entry->event = EVT_OBJ_ADDED;
+        o = usb_mtp_add_child(s, parent, name);
+        if (!o) {
+            g_free(entry);
+            return;
+        }
+        o->watchfd = watchfd;
+        trace_usb_mtp_file_monitor_event(s->dev.addr, name, "Obj Added");
+        break;
 
+    case QFILE_MONITOR_EVENT_DELETED:
         /*
-         * TODO: Ignore initiator initiated events.
-         * For now we are good because the store is RO
+         * The kernel issues a IN_IGNORED event
+         * when a dir containing a watchpoint is
+         * deleted, so we don't have to delete the
+         * watchpoint
          */
-        while (bytes > 0) {
-            char *p = buf + pos;
-            struct inotify_event *event = (struct inotify_event *)p;
-            int watchfd = 0;
-            uint32_t mask = event->mask & (IN_CREATE | IN_DELETE |
-                                           IN_MODIFY | IN_IGNORED);
-            MTPObject *parent = usb_mtp_object_lookup_wd(s, event->wd);
-            MTPMonEntry *entry = NULL;
-            MTPObject *o;
-
-            pos = pos + sizeof(struct inotify_event) + event->len;
-            bytes = bytes - pos;
-
-            if (!parent) {
-                continue;
-            }
-
-            switch (mask) {
-            case IN_CREATE:
-                if (usb_mtp_object_lookup_name
-                    (parent, event->name, event->len)) {
-                    /* Duplicate create event */
-                    continue;
-                }
-                entry = g_new0(MTPMonEntry, 1);
-                entry->handle = s->next_handle;
-                entry->event = EVT_OBJ_ADDED;
-                o = usb_mtp_add_child(s, parent, event->name);
-                if (!o) {
-                    g_free(entry);
-                    continue;
-                }
-                o->watchfd = watchfd;
-                trace_usb_mtp_inotify_event(s->dev.addr, event->name,
-                                            event->mask, "Obj Added");
-                break;
-
-            case IN_DELETE:
-                /*
-                 * The kernel issues a IN_IGNORED event
-                 * when a dir containing a watchpoint is
-                 * deleted, so we don't have to delete the
-                 * watchpoint
-                 */
-                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
-                if (!o) {
-                    continue;
-                }
-                entry = g_new0(MTPMonEntry, 1);
-                entry->handle = o->handle;
-                entry->event = EVT_OBJ_REMOVED;
-                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
-                                      event->mask, "Obj Deleted");
-                usb_mtp_object_free(s, o);
-                break;
-
-            case IN_MODIFY:
-                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
-                if (!o) {
-                    continue;
-                }
-                entry = g_new0(MTPMonEntry, 1);
-                entry->handle = o->handle;
-                entry->event = EVT_OBJ_INFO_CHANGED;
-                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
-                                      event->mask, "Obj Modified");
-                break;
-
-            case IN_IGNORED:
-                trace_usb_mtp_inotify_event(s->dev.addr, parent->path,
-                                      event->mask, "Obj parent dir ignored");
-                break;
-
-            default:
-                fprintf(stderr, "usb-mtp: failed to parse inotify event\n");
-                continue;
-            }
+        o = usb_mtp_object_lookup_name(parent, name, -1);
+        if (!o) {
+            return;
+        }
+        entry = g_new0(MTPMonEntry, 1);
+        entry->handle = o->handle;
+        entry->event = EVT_OBJ_REMOVED;
+        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Deleted");
+        usb_mtp_object_free(s, o);
+        break;
 
-            if (entry) {
-                QTAILQ_INSERT_HEAD(&s->events, entry, next);
-            }
+    case QFILE_MONITOR_EVENT_MODIFIED:
+        o = usb_mtp_object_lookup_name(parent, name, -1);
+        if (!o) {
+            return;
         }
-    }
-}
+        entry = g_new0(MTPMonEntry, 1);
+        entry->handle = o->handle;
+        entry->event = EVT_OBJ_INFO_CHANGED;
+        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Modified");
+        break;
 
-static int usb_mtp_inotify_init(MTPState *s)
-{
-    int fd;
+    case QFILE_MONITOR_EVENT_IGNORED:
+        trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path,
+                                    "Obj parent dir ignored");
+        break;
 
-    fd = inotify_init1(IN_NONBLOCK);
-    if (fd == -1) {
-        return 1;
+    default:
+        g_assert_not_reached();
     }
 
-    QTAILQ_INIT(&s->events);
-    s->inotifyfd = fd;
-
-    qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
-
-    return 0;
+    if (entry) {
+        QTAILQ_INSERT_HEAD(&s->events, entry, next);
+    }
 }
 
-static void usb_mtp_inotify_cleanup(MTPState *s)
+static void usb_mtp_file_monitor_cleanup(MTPState *s)
 {
     MTPMonEntry *e, *p;
 
-    if (!s->inotifyfd) {
-        return;
-    }
-
-    qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
-    close(s->inotifyfd);
-
     QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
         QTAILQ_REMOVE(&s->events, e, next);
         g_free(e);
     }
 }
 
-static int usb_mtp_add_watch(int inotifyfd, const char *path)
-{
-    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
-
-    return inotify_add_watch(inotifyfd, path, mask);
-}
-#endif
 
 static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
 {
     struct dirent *entry;
     DIR *dir;
+    Error *err = NULL;
 
     if (o->have_children) {
         return;
@@ -662,16 +596,19 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
     if (!dir) {
         return;
     }
-#ifdef CONFIG_INOTIFY1
-    int watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
+
+    int watchfd = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
+                                              file_monitor_event, s, &err);
     if (watchfd == -1) {
-        fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
+        fprintf(stderr, "usb-mtp: failed to add watch for %s: %s\n", o->path,
+                error_get_pretty(err));
+        error_free(err);
     } else {
-        trace_usb_mtp_inotify_event(s->dev.addr, o->path,
-                                    0, "Watch Added");
+        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path,
+                                         "Watch Added");
         o->watchfd = watchfd;
     }
-#endif
+
     while ((entry = readdir(dir)) != NULL) {
         usb_mtp_add_child(s, o, entry->d_name);
     }
@@ -1179,13 +1116,11 @@ enum {
 /* Assumes that children, if any, have been already freed */
 static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
 {
-#ifndef CONFIG_INOTIFY1
     assert(o->nchildren == 0);
     QTAILQ_REMOVE(&s->objects, o, next);
     g_free(o->name);
     g_free(o->path);
     g_free(o);
-#endif
 }
 
 static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans)
@@ -1284,6 +1219,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
     MTPData *data_in = NULL;
     MTPObject *o = NULL;
     uint32_t nres = 0, res0 = 0;
+    Error *err = NULL;
 
     /* sanity checks */
     if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
@@ -1311,19 +1247,21 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
         trace_usb_mtp_op_open_session(s->dev.addr);
         s->session = c->argv[0];
         usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
-#ifdef CONFIG_INOTIFY1
-        if (usb_mtp_inotify_init(s)) {
-            fprintf(stderr, "usb-mtp: file monitoring init failed\n");
+
+        s->file_monitor = qemu_file_monitor_get_instance(&err);
+        if (err) {
+            fprintf(stderr, "usb-mtp: file monitoring init failed: %s\n",
+                    error_get_pretty(err));
+            error_free(err);
+        } else {
+            QTAILQ_INIT(&s->events);
         }
-#endif
         break;
     case CMD_CLOSE_SESSION:
         trace_usb_mtp_op_close_session(s->dev.addr);
         s->session = 0;
         s->next_handle = 0;
-#ifdef CONFIG_INOTIFY1
-        usb_mtp_inotify_cleanup(s);
-#endif
+        usb_mtp_file_monitor_cleanup(s);
         usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
         assert(QTAILQ_EMPTY(&s->objects));
         break;
@@ -1536,9 +1474,7 @@ static void usb_mtp_handle_reset(USBDevice *dev)
 
     trace_usb_mtp_reset(s->dev.addr);
 
-#ifdef CONFIG_INOTIFY1
-    usb_mtp_inotify_cleanup(s);
-#endif
+    usb_mtp_file_monitor_cleanup(s);
     usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
     s->session = 0;
     usb_mtp_data_free(s->data_in);
@@ -1967,7 +1903,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
         }
         break;
     case EP_EVENT:
-#ifdef CONFIG_INOTIFY1
         if (!QTAILQ_EMPTY(&s->events)) {
             struct MTPMonEntry *e = QTAILQ_LAST(&s->events, events);
             uint32_t handle;
@@ -1991,7 +1926,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
             g_free(e);
             return;
         }
-#endif
         p->status = USB_RET_NAK;
         return;
     default:
diff --git a/hw/usb/trace-events b/hw/usb/trace-events
index 2c18770ca5..99b1e8b8ce 100644
--- a/hw/usb/trace-events
+++ b/hw/usb/trace-events
@@ -237,7 +237,7 @@ usb_mtp_op_unknown(int dev, uint32_t code) "dev %d, command code 0x%x"
 usb_mtp_object_alloc(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
 usb_mtp_object_free(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
 usb_mtp_add_child(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
-usb_mtp_inotify_event(int dev, const char *path, uint32_t mask, const char *s) "dev %d, path %s mask 0x%x event %s"
+usb_mtp_file_monitor_event(int dev, const char *path, const char *s) "dev %d, path %s event %s"
 
 # hw/usb/host-libusb.c
 usb_host_open_started(int bus, int addr) "dev %d:%d"
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (4 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 22:23   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks Daniel P. Berrangé
                   ` (4 subsequent siblings)
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

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

The current qemu_acl module provides a simple access control list
facility inside QEMU, which is used via a set of monitor commands
acl_show, acl_policy, acl_add, acl_remove & acl_reset.

Note there is no ability to create ACLs - the network services (eg VNC
server) were expected to create ACLs that they want to check.

There is also no way to define ACLs on the command line, nor potentially
integrate with external authorization systems like polkit, pam, ldap
lookup, etc.

The QAuthZ object defines a minimal abstract QOM class that can be
subclassed for creating different authorization providers.

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile             |  10 ++--
 Makefile.objs        |   6 +++
 Makefile.target      |   2 +
 include/authz/base.h | 112 +++++++++++++++++++++++++++++++++++++++++++
 authz/base.c         |  82 +++++++++++++++++++++++++++++++
 MAINTAINERS          |   7 +++
 authz/Makefile.objs  |   1 +
 authz/trace-events   |   4 ++
 8 files changed, 220 insertions(+), 4 deletions(-)
 create mode 100644 include/authz/base.h
 create mode 100644 authz/base.c
 create mode 100644 authz/Makefile.objs
 create mode 100644 authz/trace-events

diff --git a/Makefile b/Makefile
index f2947186a4..4b20ee2b19 100644
--- a/Makefile
+++ b/Makefile
@@ -412,6 +412,7 @@ endif
 
 dummy := $(call unnest-vars,, \
                 stub-obj-y \
+                authz-obj-y \
                 chardev-obj-y \
                 util-obj-y \
                 qga-obj-y \
@@ -474,6 +475,7 @@ qemu-options.def: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
 SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
 SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
 
+$(SOFTMMU_SUBDIR_RULES): $(authz-obj-y)
 $(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
 $(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
 $(SOFTMMU_SUBDIR_RULES): $(io-obj-y)
@@ -536,9 +538,9 @@ COMMON_LDADDS = libqemuutil.a
 
 qemu-img.o: qemu-img-cmds.h
 
-qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
-qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-img$(EXESUF): qemu-img.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-nbd$(EXESUF): qemu-nbd.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+qemu-io$(EXESUF): qemu-io.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
 
 qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
 
@@ -549,7 +551,7 @@ qemu-edid$(EXESUF): qemu-edid.o hw/display/edid-generate.o $(COMMON_LDADDS)
 fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
 fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
 
-scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
+scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(authz-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
 ifdef CONFIG_MPATH
 scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist
 endif
diff --git a/Makefile.objs b/Makefile.objs
index 1e1ff387d7..ecb1071c4f 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -62,6 +62,11 @@ util-obj-y += qapi/qapi-introspect.o
 
 chardev-obj-y = chardev/
 
+#######################################################################
+# authz-obj-y is code used by both qemu system emulation and qemu-img
+
+authz-obj-y = authz/
+
 #######################################################################
 # block-obj-y is code used by both qemu system emulation and qemu-img
 
@@ -200,6 +205,7 @@ trace-events-subdirs =
 trace-events-subdirs += accel/kvm
 trace-events-subdirs += accel/tcg
 trace-events-subdirs += audio
+trace-events-subdirs += authz
 trace-events-subdirs += block
 trace-events-subdirs += chardev
 trace-events-subdirs += crypto
diff --git a/Makefile.target b/Makefile.target
index 4d56298bbf..c08461726d 100644
--- a/Makefile.target
+++ b/Makefile.target
@@ -172,6 +172,7 @@ include $(SRC_PATH)/Makefile.objs
 dummy := $(call unnest-vars,,target-obj-y)
 target-obj-y-save := $(target-obj-y)
 dummy := $(call unnest-vars,.., \
+               authz-obj-y \
                block-obj-y \
                block-obj-m \
                chardev-obj-y \
@@ -185,6 +186,7 @@ target-obj-y := $(target-obj-y-save)
 all-obj-y += $(common-obj-y)
 all-obj-y += $(target-obj-y)
 all-obj-y += $(qom-obj-y)
+all-obj-$(CONFIG_SOFTMMU) += $(authz-obj-y)
 all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
 all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
 all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
diff --git a/include/authz/base.h b/include/authz/base.h
new file mode 100644
index 0000000000..77dcd54c4c
--- /dev/null
+++ b/include/authz/base.h
@@ -0,0 +1,112 @@
+/*
+ * QEMU authorization framework base class
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_BASE_H__
+#define QAUTHZ_BASE_H__
+
+#include "qemu-common.h"
+#include "qapi/error.h"
+#include "qom/object.h"
+
+
+#define TYPE_QAUTHZ "authz"
+
+#define QAUTHZ_CLASS(klass) \
+     OBJECT_CLASS_CHECK(QAuthZClass, (klass), \
+                        TYPE_QAUTHZ)
+#define QAUTHZ_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(QAuthZClass, (obj), \
+                      TYPE_QAUTHZ)
+#define QAUTHZ(obj) \
+     INTERFACE_CHECK(QAuthZ, (obj), \
+                     TYPE_QAUTHZ)
+
+typedef struct QAuthZ QAuthZ;
+typedef struct QAuthZClass QAuthZClass;
+
+/**
+ * QAuthZ:
+ *
+ * The QAuthZ class defines an API contract to be used
+ * for providing an authorization driver for services
+ * with user identities.
+ */
+
+struct QAuthZ {
+    Object parent_obj;
+};
+
+
+struct QAuthZClass {
+    ObjectClass parent_class;
+
+    bool (*is_allowed)(QAuthZ *authz,
+                       const char *identity,
+                       Error **errp);
+};
+
+
+/**
+ * qauthz_is_allowed:
+ * @authz: the authorization object
+ * @identity: the user identity to authorize
+ * @errp: pointer to a NULL initialized error object
+ *
+ * Check if a user @identity is authorized. If an error
+ * occurs this method will return false to indicate
+ * denial, as well as setting @errp to contain the details.
+ * Callers are recommended to treat the denial and error
+ * scenarios identically. Specifically the error info in
+ * @errp should never be fed back to the user being
+ * authorized, it is merely for benefit of administrator
+ * debugging.
+ *
+ * Returns: true if @identity is authorized, false if denied or if
+ * an error occurred.
+ */
+bool qauthz_is_allowed(QAuthZ *authz,
+                       const char *identity,
+                       Error **errp);
+
+
+/**
+ * qauthz_is_allowed_by_id:
+ * @authzid: ID of the authorization object
+ * @identity: the user identity to authorize
+ * @errp: pointer to a NULL initialized error object
+ *
+ * Check if a user @identity is authorized. If an error
+ * occurs this method will return false to indicate
+ * denial, as well as setting @errp to contain the details.
+ * Callers are recommended to treat the denial and error
+ * scenarios identically. Specifically the error info in
+ * @errp should never be fed back to the user being
+ * authorized, it is merely for benefit of administrator
+ * debugging.
+ *
+ * Returns: true if @identity is authorized, false if denied or if
+ * an error occurred.
+ */
+bool qauthz_is_allowed_by_id(const char *authzid,
+                             const char *identity,
+                             Error **errp);
+
+#endif /* QAUTHZ_BASE_H__ */
+
diff --git a/authz/base.c b/authz/base.c
new file mode 100644
index 0000000000..110dfa4195
--- /dev/null
+++ b/authz/base.c
@@ -0,0 +1,82 @@
+/*
+ * QEMU authorization framework base class
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/base.h"
+#include "authz/trace.h"
+
+bool qauthz_is_allowed(QAuthZ *authz,
+                       const char *identity,
+                       Error **errp)
+{
+    QAuthZClass *cls = QAUTHZ_GET_CLASS(authz);
+    bool allowed;
+
+    allowed = cls->is_allowed(authz, identity, errp);
+    trace_qauthz_is_allowed(authz, identity, allowed);
+
+    return allowed;
+}
+
+
+bool qauthz_is_allowed_by_id(const char *authzid,
+                             const char *identity,
+                             Error **errp)
+{
+    QAuthZ *authz;
+    Object *obj;
+    Object *container;
+
+    container = object_get_objects_root();
+    obj = object_resolve_path_component(container,
+                                        authzid);
+    if (!obj) {
+        error_setg(errp, "Cannot find QAuthZ object ID %s",
+                   authzid);
+        return false;
+    }
+
+    if (!object_dynamic_cast(obj, TYPE_QAUTHZ)) {
+        error_setg(errp, "Object '%s' is not a QAuthZ subclass",
+                   authzid);
+        return false;
+    }
+
+    authz = QAUTHZ(obj);
+
+    return qauthz_is_allowed(authz, identity, errp);
+}
+
+
+static const TypeInfo authz_info = {
+    .parent = TYPE_OBJECT,
+    .name = TYPE_QAUTHZ,
+    .instance_size = sizeof(QAuthZ),
+    .class_size = sizeof(QAuthZClass),
+    .abstract = true,
+};
+
+static void qauthz_register_types(void)
+{
+    type_register_static(&authz_info);
+}
+
+type_init(qauthz_register_types)
+
diff --git a/MAINTAINERS b/MAINTAINERS
index 29bbcf8c25..9624734923 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1857,6 +1857,13 @@ F: io/
 F: include/io/
 F: tests/test-io-*
 
+User authorization
+M: Daniel P. Berrange <berrange@redhat.com>
+S: Maintained
+F: authz/
+F: include/authz/
+F: tests/test-authz-*
+
 Sockets
 M: Daniel P. Berrange <berrange@redhat.com>
 M: Gerd Hoffmann <kraxel@redhat.com>
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
new file mode 100644
index 0000000000..12597c9528
--- /dev/null
+++ b/authz/Makefile.objs
@@ -0,0 +1 @@
+authz-obj-y += base.o
diff --git a/authz/trace-events b/authz/trace-events
new file mode 100644
index 0000000000..481c90f511
--- /dev/null
+++ b/authz/trace-events
@@ -0,0 +1,4 @@
+# See docs/devel/tracing.txt for syntax documentation.
+
+# authz/base.c
+qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (5 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-10-22 23:54   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
                   ` (3 subsequent siblings)
  10 siblings, 2 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

In many cases a single VM will just need to whilelist a single identity
as the allowed user of network services. This is especially the case for
TLS live migration (optionally with NBD storage) where we just need to
whitelist the x509 certificate distinguished name of the source QEMU
host.

Via QMP this can be configured with:

  {
    "execute": "object-add",
    "arguments": {
      "qom-type": "authz-simple",
      "id": "authz0",
      "parameters": {
        "identity": "fred"
      }
    }
  }

Or via the command line

  -object authz-simple,id=authz0,identity=fred

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/authz/simple.h |  84 ++++++++++++++++++++++++++++++
 authz/simple.c         | 115 +++++++++++++++++++++++++++++++++++++++++
 authz/Makefile.objs    |   1 +
 authz/trace-events     |   3 ++
 qemu-options.hx        |  24 +++++++++
 5 files changed, 227 insertions(+)
 create mode 100644 include/authz/simple.h
 create mode 100644 authz/simple.c

diff --git a/include/authz/simple.h b/include/authz/simple.h
new file mode 100644
index 0000000000..4686e7676d
--- /dev/null
+++ b/include/authz/simple.h
@@ -0,0 +1,84 @@
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_SIMPLE_H__
+#define QAUTHZ_SIMPLE_H__
+
+#include "authz/base.h"
+
+#define TYPE_QAUTHZ_SIMPLE "authz-simple"
+
+#define QAUTHZ_SIMPLE_CLASS(klass)                        \
+    OBJECT_CLASS_CHECK(QAuthZSimpleClass, (klass),        \
+                       TYPE_QAUTHZ_SIMPLE)
+#define QAUTHZ_SIMPLE_GET_CLASS(obj)              \
+    OBJECT_GET_CLASS(QAuthZSimpleClass, (obj),    \
+                      TYPE_QAUTHZ_SIMPLE)
+#define QAUTHZ_SIMPLE(obj) \
+    INTERFACE_CHECK(QAuthZSimple, (obj),          \
+                    TYPE_QAUTHZ_SIMPLE)
+
+typedef struct QAuthZSimple QAuthZSimple;
+typedef struct QAuthZSimpleClass QAuthZSimpleClass;
+
+
+/**
+ * QAuthZSimple:
+ *
+ * This authorization driver provides a simple mechanism
+ * for granting access based on an exact matched username.
+ *
+ * To create an instance of this class via QMP:
+ *
+ *  {
+ *    "execute": "object-add",
+ *    "arguments": {
+ *      "qom-type": "authz-simple",
+ *      "id": "authz0",
+ *      "parameters": {
+ *        "identity": "fred"
+ *      }
+ *    }
+ *  }
+ *
+ * Or via the command line
+ *
+ *   -object authz-simple,id=authz0,identity=fred
+ *
+ */
+struct QAuthZSimple {
+    QAuthZ parent_obj;
+
+    char *identity;
+};
+
+
+struct QAuthZSimpleClass {
+    QAuthZClass parent_class;
+};
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+                                const char *identity,
+                                Error **errp);
+
+
+#endif /* QAUTHZ_SIMPLE_H__ */
+
diff --git a/authz/simple.c b/authz/simple.c
new file mode 100644
index 0000000000..8ab718803e
--- /dev/null
+++ b/authz/simple.c
@@ -0,0 +1,115 @@
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/simple.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+
+static bool qauthz_simple_is_allowed(QAuthZ *authz,
+                                     const char *identity,
+                                     Error **errp)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(authz);
+
+    trace_qauthz_simple_is_allowed(authz, sauthz->identity, identity);
+    return g_str_equal(identity, sauthz->identity);
+}
+
+static void
+qauthz_simple_prop_set_identity(Object *obj,
+                                const char *value,
+                                Error **errp G_GNUC_UNUSED)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    g_free(sauthz->identity);
+    sauthz->identity = g_strdup(value);
+}
+
+
+static char *
+qauthz_simple_prop_get_identity(Object *obj,
+                                Error **errp G_GNUC_UNUSED)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    return g_strdup(sauthz->identity);
+}
+
+
+static void
+qauthz_simple_finalize(Object *obj)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    g_free(sauthz->identity);
+}
+
+
+static void
+qauthz_simple_class_init(ObjectClass *oc, void *data)
+{
+    QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+    authz->is_allowed = qauthz_simple_is_allowed;
+
+    object_class_property_add_str(oc, "identity",
+                                  qauthz_simple_prop_get_identity,
+                                  qauthz_simple_prop_set_identity,
+                                  NULL);
+}
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+                                const char *identity,
+                                Error **errp)
+{
+    return QAUTHZ_SIMPLE(
+        object_new_with_props(TYPE_QAUTHZ_SIMPLE,
+                              object_get_objects_root(),
+                              id, errp,
+                              "identity", identity,
+                              NULL));
+}
+
+
+static const TypeInfo qauthz_simple_info = {
+    .parent = TYPE_QAUTHZ,
+    .name = TYPE_QAUTHZ_SIMPLE,
+    .instance_size = sizeof(QAuthZSimple),
+    .instance_finalize = qauthz_simple_finalize,
+    .class_size = sizeof(QAuthZSimpleClass),
+    .class_init = qauthz_simple_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+
+static void
+qauthz_simple_register_types(void)
+{
+    type_register_static(&qauthz_simple_info);
+}
+
+
+type_init(qauthz_simple_register_types);
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
index 12597c9528..2a75d53840 100644
--- a/authz/Makefile.objs
+++ b/authz/Makefile.objs
@@ -1 +1,2 @@
 authz-obj-y += base.o
+authz-obj-y += simple.o
diff --git a/authz/trace-events b/authz/trace-events
index 481c90f511..1ef796c1e1 100644
--- a/authz/trace-events
+++ b/authz/trace-events
@@ -2,3 +2,6 @@
 
 # authz/base.c
 qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
+
+# auth/simple.c
+qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
diff --git a/qemu-options.hx b/qemu-options.hx
index f139459e80..68eaf39cc4 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4377,6 +4377,30 @@ e.g to launch a SEV guest
      .....
 
 @end example
+
+
+@item -object authz-simple,id=@var{id},identity=@var{string}
+
+Create an authorization object that will control access to network services.
+
+The @option{identity} parameter is identifies the user and its format
+depends on the network service that authorization object is associated
+with. For authorizing based on TLS x509 certificates, the identity must
+be the x509 distinguished name. Note that care must be taken to escape
+any commas in the distinguished name.
+
+An example authorization object to validate a x509 distinguished name
+would look like:
+@example
+ # $QEMU \
+     ...
+     -object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,O=Example Org,,L=London,,ST=London,,C=GB' \
+     ...
+@end example
+
+Note the use of quotes due to the x509 distinguished name containing
+whitespace, and escaping of ','.
+
 @end table
 
 ETEXI
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (6 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-10-23 10:18   ` Philippe Mathieu-Daudé
                     ` (2 more replies)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file " Daniel P. Berrangé
                   ` (2 subsequent siblings)
  10 siblings, 3 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

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

Add a QAuthZList object type that implements the QAuthZ interface. This
built-in implementation maintains a trivial access control list with a
sequence of match rules and a final default policy. This replicates the
functionality currently provided by the qemu_acl module.

To create an instance of this object via the QMP monitor, the syntax
used would be:

  {
    "execute": "object-add",
    "arguments": {
      "qom-type": "authz-list",
      "id": "authz0",
      "parameters": {
        "rules": [
           { "match": "fred", "policy": "allow", "format": "exact" },
           { "match": "bob", "policy": "allow", "format": "exact" },
           { "match": "danb", "policy": "deny", "format": "glob" },
           { "match": "dan*", "policy": "allow", "format": "exact" },
        ],
        "policy": "deny"
      }
    }
  }

This sets up an authorization rule that allows 'fred', 'bob' and anyone
whose name starts with 'dan', except for 'danb'. Everyone unmatched is
denied.

It is not currently possible to create this via -object, since there is
no syntax supported to specify non-scalar properties for objects. This
is likely to be addressed by later support for using JSON with -object,
or an equivalent approach.

In any case the future "authz-listfile" object can be used from the
CLI and is likely a better choice, as it allows the ACL to be refreshed
automatically on change.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile                |   7 +-
 Makefile.objs           |   4 +
 qapi/authz.json         |  58 ++++++++
 qapi/qapi-schema.json   |   1 +
 include/authz/list.h    | 106 ++++++++++++++
 authz/list.c            | 309 ++++++++++++++++++++++++++++++++++++++++
 tests/test-authz-list.c | 171 ++++++++++++++++++++++
 .gitignore              |   4 +
 MAINTAINERS             |   1 +
 authz/Makefile.objs     |   1 +
 authz/trace-events      |   4 +
 tests/Makefile.include  |   4 +
 12 files changed, 669 insertions(+), 1 deletion(-)
 create mode 100644 qapi/authz.json
 create mode 100644 include/authz/list.h
 create mode 100644 authz/list.c
 create mode 100644 tests/test-authz-list.c

diff --git a/Makefile b/Makefile
index 4b20ee2b19..da9ea40725 100644
--- a/Makefile
+++ b/Makefile
@@ -600,7 +600,8 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/tpm.json \
                $(SRC_PATH)/qapi/trace.json \
                $(SRC_PATH)/qapi/transaction.json \
-               $(SRC_PATH)/qapi/ui.json
+               $(SRC_PATH)/qapi/ui.json \
+               $(SRC_PATH)/qapi/authz.json
 
 qapi/qapi-builtin-types.c qapi/qapi-builtin-types.h \
 qapi/qapi-types.c qapi/qapi-types.h \
@@ -621,6 +622,7 @@ qapi/qapi-types-tpm.c qapi/qapi-types-tpm.h \
 qapi/qapi-types-trace.c qapi/qapi-types-trace.h \
 qapi/qapi-types-transaction.c qapi/qapi-types-transaction.h \
 qapi/qapi-types-ui.c qapi/qapi-types-ui.h \
+qapi/qapi-types-authz.c qapi/qapi-types-authz.h \
 qapi/qapi-builtin-visit.c qapi/qapi-builtin-visit.h \
 qapi/qapi-visit.c qapi/qapi-visit.h \
 qapi/qapi-visit-block-core.c qapi/qapi-visit-block-core.h \
@@ -640,6 +642,7 @@ qapi/qapi-visit-tpm.c qapi/qapi-visit-tpm.h \
 qapi/qapi-visit-trace.c qapi/qapi-visit-trace.h \
 qapi/qapi-visit-transaction.c qapi/qapi-visit-transaction.h \
 qapi/qapi-visit-ui.c qapi/qapi-visit-ui.h \
+qapi/qapi-visit-authz.c qapi/qapi-visit-authz.h \
 qapi/qapi-commands.h qapi/qapi-commands.c \
 qapi/qapi-commands-block-core.c qapi/qapi-commands-block-core.h \
 qapi/qapi-commands-block.c qapi/qapi-commands-block.h \
@@ -658,6 +661,7 @@ qapi/qapi-commands-tpm.c qapi/qapi-commands-tpm.h \
 qapi/qapi-commands-trace.c qapi/qapi-commands-trace.h \
 qapi/qapi-commands-transaction.c qapi/qapi-commands-transaction.h \
 qapi/qapi-commands-ui.c qapi/qapi-commands-ui.h \
+qapi/qapi-commands-authz.c qapi/qapi-commands-authz.h \
 qapi/qapi-events.c qapi/qapi-events.h \
 qapi/qapi-events-block-core.c qapi/qapi-events-block-core.h \
 qapi/qapi-events-block.c qapi/qapi-events-block.h \
@@ -676,6 +680,7 @@ qapi/qapi-events-tpm.c qapi/qapi-events-tpm.h \
 qapi/qapi-events-trace.c qapi/qapi-events-trace.h \
 qapi/qapi-events-transaction.c qapi/qapi-events-transaction.h \
 qapi/qapi-events-ui.c qapi/qapi-events-ui.h \
+qapi/qapi-events-authz.c qapi/qapi-events-authz.h \
 qapi/qapi-introspect.h qapi/qapi-introspect.c \
 qapi/qapi-doc.texi: \
 qapi-gen-timestamp ;
diff --git a/Makefile.objs b/Makefile.objs
index ecb1071c4f..825c5863ac 100644
--- a/Makefile.objs
+++ b/Makefile.objs
@@ -21,6 +21,7 @@ util-obj-y += qapi/qapi-types-tpm.o
 util-obj-y += qapi/qapi-types-trace.o
 util-obj-y += qapi/qapi-types-transaction.o
 util-obj-y += qapi/qapi-types-ui.o
+util-obj-y += qapi/qapi-types-authz.o
 util-obj-y += qapi/qapi-builtin-visit.o
 util-obj-y += qapi/qapi-visit.o
 util-obj-y += qapi/qapi-visit-block-core.o
@@ -40,6 +41,7 @@ util-obj-y += qapi/qapi-visit-tpm.o
 util-obj-y += qapi/qapi-visit-trace.o
 util-obj-y += qapi/qapi-visit-transaction.o
 util-obj-y += qapi/qapi-visit-ui.o
+util-obj-y += qapi/qapi-visit-authz.o
 util-obj-y += qapi/qapi-events.o
 util-obj-y += qapi/qapi-events-block-core.o
 util-obj-y += qapi/qapi-events-block.o
@@ -58,6 +60,7 @@ util-obj-y += qapi/qapi-events-tpm.o
 util-obj-y += qapi/qapi-events-trace.o
 util-obj-y += qapi/qapi-events-transaction.o
 util-obj-y += qapi/qapi-events-ui.o
+util-obj-y += qapi/qapi-events-authz.o
 util-obj-y += qapi/qapi-introspect.o
 
 chardev-obj-y = chardev/
@@ -160,6 +163,7 @@ common-obj-y += qapi/qapi-commands-tpm.o
 common-obj-y += qapi/qapi-commands-trace.o
 common-obj-y += qapi/qapi-commands-transaction.o
 common-obj-y += qapi/qapi-commands-ui.o
+common-obj-y += qapi/qapi-commands-authz.o
 common-obj-y += qapi/qapi-introspect.o
 common-obj-y += qmp.o hmp.o
 endif
diff --git a/qapi/authz.json b/qapi/authz.json
new file mode 100644
index 0000000000..607839c627
--- /dev/null
+++ b/qapi/authz.json
@@ -0,0 +1,58 @@
+# -*- Mode: Python -*-
+#
+# QAPI authz definitions
+
+##
+# @QAuthZListPolicy:
+#
+# The authorization policy result
+#
+# @deny: deny access
+# @allow: allow access
+#
+# Since: 3.0
+##
+{ 'enum': 'QAuthZListPolicy',
+  'prefix': 'QAUTHZ_LIST_POLICY',
+  'data': ['deny', 'allow']}
+
+##
+# @QAuthZListFormat:
+#
+# The authorization policy result
+#
+# @exact: an exact string match
+# @glob: string with ? and * shell wildcard support
+#
+# Since: 3.0
+##
+{ 'enum': 'QAuthZListFormat',
+  'prefix': 'QAUTHZ_LIST_FORMAT',
+  'data': ['exact', 'glob']}
+
+##
+# @QAuthZListRule:
+#
+# A single authorization rule.
+#
+# @match: a glob to match against a user identity
+# @policy: the result to return if @match evaluates to true
+# @format: (optional) the format of the @match rule (default 'exact')
+#
+# Since: 3.0
+##
+{ 'struct': 'QAuthZListRule',
+  'data': {'match': 'str',
+           'policy': 'QAuthZListPolicy',
+           '*format': 'QAuthZListFormat'}}
+
+##
+# @QAuthZListRuleListHack:
+#
+# Not exposed via QMP; hack to generate QAuthZListRuleList
+# for use internally by the code.
+#
+# Since: 3.0
+##
+{ 'struct': 'QAuthZListRuleListHack',
+  'data': { 'unused': ['QAuthZListRule'] } }
diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
index 65b6dc2f6f..6a5a02a388 100644
--- a/qapi/qapi-schema.json
+++ b/qapi/qapi-schema.json
@@ -89,6 +89,7 @@
 { 'include': 'rocker.json' }
 { 'include': 'tpm.json' }
 { 'include': 'ui.json' }
+{ 'include': 'authz.json' }
 { 'include': 'migration.json' }
 { 'include': 'transaction.json' }
 { 'include': 'trace.json' }
diff --git a/include/authz/list.h b/include/authz/list.h
new file mode 100644
index 0000000000..eb131ba0f0
--- /dev/null
+++ b/include/authz/list.h
@@ -0,0 +1,106 @@
+/*
+ * QEMU list authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_LIST_H__
+#define QAUTHZ_LIST_H__
+
+#include "authz/base.h"
+#include "qapi/qapi-types-authz.h"
+
+#define TYPE_QAUTHZ_LIST "authz-list"
+
+#define QAUTHZ_LIST_CLASS(klass)                        \
+    OBJECT_CLASS_CHECK(QAuthZListClass, (klass),        \
+                       TYPE_QAUTHZ_LIST)
+#define QAUTHZ_LIST_GET_CLASS(obj)              \
+    OBJECT_GET_CLASS(QAuthZListClass, (obj),    \
+                      TYPE_QAUTHZ_LIST)
+#define QAUTHZ_LIST(obj) \
+    INTERFACE_CHECK(QAuthZList, (obj),          \
+                    TYPE_QAUTHZ_LIST)
+
+typedef struct QAuthZList QAuthZList;
+typedef struct QAuthZListClass QAuthZListClass;
+
+
+/**
+ * QAuthZList:
+ *
+ * This authorization driver provides a list mechanism
+ * for granting access by matching user names against a
+ * list of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ *  {
+ *    "execute": "object-add",
+ *    "arguments": {
+ *      "qom-type": "authz-list",
+ *      "id": "authz0",
+ *      "parameters": {
+ *        "rules": [
+ *           { "match": "fred", "policy": "allow", "format": "exact" },
+ *           { "match": "bob", "policy": "allow", "format": "exact" },
+ *           { "match": "danb", "policy": "deny", "format": "exact" },
+ *           { "match": "dan*", "policy": "allow", "format": "glob" }
+ *        ],
+ *        "policy": "deny"
+ *      }
+ *    }
+ *  }
+ *
+ */
+struct QAuthZList {
+    QAuthZ parent_obj;
+
+    QAuthZListPolicy policy;
+    QAuthZListRuleList *rules;
+};
+
+
+struct QAuthZListClass {
+    QAuthZClass parent_class;
+};
+
+
+QAuthZList *qauthz_list_new(const char *id,
+                            QAuthZListPolicy policy,
+                            Error **errp);
+
+ssize_t qauthz_list_append_rule(QAuthZList *auth,
+                                const char *match,
+                                QAuthZListPolicy policy,
+                                QAuthZListFormat format,
+                                Error **errp);
+
+ssize_t qauthz_list_insert_rule(QAuthZList *auth,
+                                const char *match,
+                                QAuthZListPolicy policy,
+                                QAuthZListFormat format,
+                                size_t index,
+                                Error **errp);
+
+ssize_t qauthz_list_delete_rule(QAuthZList *auth,
+                                const char *match);
+
+
+#endif /* QAUTHZ_LIST_H__ */
+
diff --git a/authz/list.c b/authz/list.c
new file mode 100644
index 0000000000..1d9544681c
--- /dev/null
+++ b/authz/list.c
@@ -0,0 +1,309 @@
+/*
+ * QEMU access control list authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/list.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+#include "qapi/qapi-visit-authz.h"
+
+#ifdef CONFIG_FNMATCH
+#include <fnmatch.h>
+#endif
+
+static bool qauthz_list_is_allowed(QAuthZ *authz,
+                                   const char *identity,
+                                   Error **errp)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(authz);
+    QAuthZListRuleList *rules = lauthz->rules;
+
+    while (rules) {
+        QAuthZListRule *rule = rules->value;
+        QAuthZListFormat format = rule->has_format ? rule->format :
+            QAUTHZ_LIST_FORMAT_EXACT;
+
+        trace_qauthz_list_check_rule(authz, rule->match, identity,
+                                     format, rule->policy);
+        switch (format) {
+        case QAUTHZ_LIST_FORMAT_EXACT:
+            if (strcmp(rule->match, identity) == 0) {
+                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
+            }
+            break;
+#ifdef CONFIG_FNMATCH
+        case QAUTHZ_LIST_FORMAT_GLOB:
+            if (fnmatch(rule->match, identity, 0) == 0) {
+                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
+            }
+            break;
+#else
+            return false;
+#endif
+        default:
+            return false;
+        }
+        rules = rules->next;
+    }
+
+    trace_qauthz_list_default_policy(authz, identity, lauthz->policy);
+    return lauthz->policy == QAUTHZ_LIST_POLICY_ALLOW;
+}
+
+
+static void
+qauthz_list_prop_set_policy(Object *obj,
+                            int value,
+                            Error **errp G_GNUC_UNUSED)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+    lauthz->policy = value;
+}
+
+
+static int
+qauthz_list_prop_get_policy(Object *obj,
+                            Error **errp G_GNUC_UNUSED)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+    return lauthz->policy;
+}
+
+
+static void
+qauthz_list_prop_get_rules(Object *obj, Visitor *v, const char *name,
+                           void *opaque, Error **errp)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
+}
+
+static void
+qauthz_list_prop_set_rules(Object *obj, Visitor *v, const char *name,
+                           void *opaque, Error **errp)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(obj);
+    QAuthZListRuleList *oldrules;
+#ifndef CONFIG_FNMATCH
+    QAuthZListRuleList *rules;
+#endif
+
+    oldrules = lauthz->rules;
+    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
+
+#ifndef CONFIG_FNMATCH
+    rules = lauthz->rules;
+    while (rules) {
+        QAuthZListRule *rule = rules->value;
+        if (rule->has_format &&
+            rule->format == QAUTHZ_LIST_FORMAT_GLOB) {
+            error_setg(errp, "Glob format not supported on this platform");
+            qapi_free_QAuthZListRuleList(lauthz->rules);
+            lauthz->rules = oldrules;
+            return;
+        }
+    }
+#endif
+
+    qapi_free_QAuthZListRuleList(oldrules);
+}
+
+
+static void
+qauthz_list_finalize(Object *obj)
+{
+    QAuthZList *lauthz = QAUTHZ_LIST(obj);
+
+    qapi_free_QAuthZListRuleList(lauthz->rules);
+}
+
+
+static void
+qauthz_list_class_init(ObjectClass *oc, void *data)
+{
+    QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+    object_class_property_add_enum(oc, "policy",
+                                   "QAuthZListPolicy",
+                                   &QAuthZListPolicy_lookup,
+                                   qauthz_list_prop_get_policy,
+                                   qauthz_list_prop_set_policy,
+                                   NULL);
+
+    object_class_property_add(oc, "rules", "QAuthZListRule",
+                              qauthz_list_prop_get_rules,
+                              qauthz_list_prop_set_rules,
+                              NULL, NULL, NULL);
+
+    authz->is_allowed = qauthz_list_is_allowed;
+}
+
+
+QAuthZList *qauthz_list_new(const char *id,
+                            QAuthZListPolicy policy,
+                            Error **errp)
+{
+    return QAUTHZ_LIST(
+        object_new_with_props(TYPE_QAUTHZ_LIST,
+                              object_get_objects_root(),
+                              id, errp,
+                              "policy", QAuthZListPolicy_lookup.array[policy],
+                              NULL));
+}
+
+ssize_t qauthz_list_append_rule(QAuthZList *auth,
+                                const char *match,
+                                QAuthZListPolicy policy,
+                                QAuthZListFormat format,
+                                Error **errp)
+{
+    QAuthZListRule *rule;
+    QAuthZListRuleList *rules, *tmp;
+    size_t i = 0;
+
+#ifndef CONFIG_FNMATCH
+    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
+        error_setg(errp, "Glob format not supported on this platform");
+        return -1;
+    }
+#endif
+
+    rule = g_new0(QAuthZListRule, 1);
+    rule->policy = policy;
+    rule->match = g_strdup(match);
+    rule->format = format;
+    rule->has_format = true;
+
+    tmp = g_new0(QAuthZListRuleList, 1);
+    tmp->value = rule;
+
+    rules = auth->rules;
+    if (rules) {
+        while (rules->next) {
+            i++;
+            rules = rules->next;
+        }
+        rules->next = tmp;
+        return i + 1;
+    } else {
+        auth->rules = tmp;
+        return 0;
+    }
+}
+
+
+ssize_t qauthz_list_insert_rule(QAuthZList *auth,
+                                const char *match,
+                                QAuthZListPolicy policy,
+                                QAuthZListFormat format,
+                                size_t index,
+                                Error **errp)
+{
+    QAuthZListRule *rule;
+    QAuthZListRuleList *rules, *tmp;
+    size_t i = 0;
+
+#ifndef CONFIG_FNMATCH
+    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
+        error_setg(errp, "Glob format not supported on this platform");
+        return -1;
+    }
+#endif
+
+    rule = g_new0(QAuthZListRule, 1);
+    rule->policy = policy;
+    rule->match = g_strdup(match);
+    rule->format = format;
+    rule->has_format = true;
+
+    tmp = g_new0(QAuthZListRuleList, 1);
+    tmp->value = rule;
+
+    rules = auth->rules;
+    if (rules && index > 0) {
+        while (rules->next && i < (index - 1)) {
+            i++;
+            rules = rules->next;
+        }
+        tmp->next = rules->next;
+        rules->next = tmp;
+        return i + 1;
+    } else {
+        tmp->next = auth->rules;
+        auth->rules = tmp;
+        return 0;
+    }
+}
+
+
+ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
+{
+    QAuthZListRule *rule;
+    QAuthZListRuleList *rules, *prev;
+    size_t i = 0;
+
+    prev = NULL;
+    rules = auth->rules;
+    while (rules) {
+        rule = rules->value;
+        if (g_str_equal(rule->match, match)) {
+            if (prev) {
+                prev->next = rules->next;
+            } else {
+                auth->rules = rules->next;
+            }
+            rules->next = NULL;
+            qapi_free_QAuthZListRuleList(rules);
+            return i;
+        }
+        prev = rules;
+        rules = rules->next;
+        i++;
+    }
+
+    return -1;
+}
+
+
+static const TypeInfo qauthz_list_info = {
+    .parent = TYPE_QAUTHZ,
+    .name = TYPE_QAUTHZ_LIST,
+    .instance_size = sizeof(QAuthZList),
+    .instance_finalize = qauthz_list_finalize,
+    .class_size = sizeof(QAuthZListClass),
+    .class_init = qauthz_list_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+
+static void
+qauthz_list_register_types(void)
+{
+    type_register_static(&qauthz_list_info);
+}
+
+
+type_init(qauthz_list_register_types);
diff --git a/tests/test-authz-list.c b/tests/test-authz-list.c
new file mode 100644
index 0000000000..02ce4c5763
--- /dev/null
+++ b/tests/test-authz-list.c
@@ -0,0 +1,171 @@
+/*
+ * QEMU list authorization object
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+
+#include "authz/list.h"
+
+static void test_authz_default_deny(void)
+{
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_DENY,
+                                       &error_abort);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+static void test_authz_default_allow(void)
+{
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_ALLOW,
+                                       &error_abort);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_deny(void)
+{
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_ALLOW,
+                                       &error_abort);
+
+    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
+                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_allow(void)
+{
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_DENY,
+                                       &error_abort);
+
+    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
+                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+
+static void test_authz_complex(void)
+{
+#ifndef CONFIG_FNMATCH
+    Error *local_err = NULL;
+#endif
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_DENY,
+                                       &error_abort);
+
+    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
+                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+    qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
+                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+    qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
+                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
+#ifdef CONFIG_FNMATCH
+    qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
+                            QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
+#else
+    g_assert(qauthz_list_append_rule(auth, "dan*",
+                                     QAUTHZ_LIST_POLICY_ALLOW,
+                                     QAUTHZ_LIST_FORMAT_GLOB,
+                                     &local_err) < 0);
+    g_assert(local_err != NULL);
+    error_free(local_err);
+#endif
+
+    object_unparent(OBJECT(auth));
+}
+
+static void test_authz_add_remove(void)
+{
+    QAuthZList *auth = qauthz_list_new("auth0",
+                                       QAUTHZ_LIST_POLICY_ALLOW,
+                                       &error_abort);
+
+    g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
+                                            QAUTHZ_LIST_POLICY_ALLOW,
+                                            QAUTHZ_LIST_FORMAT_EXACT,
+                                            &error_abort),
+                    ==, 0);
+    g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
+                                            QAUTHZ_LIST_POLICY_ALLOW,
+                                            QAUTHZ_LIST_FORMAT_EXACT,
+                                            &error_abort),
+                    ==, 1);
+    g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
+                                            QAUTHZ_LIST_POLICY_DENY,
+                                            QAUTHZ_LIST_FORMAT_EXACT,
+                                            &error_abort),
+                    ==, 2);
+    g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
+                                            QAUTHZ_LIST_POLICY_DENY,
+                                            QAUTHZ_LIST_FORMAT_EXACT,
+                                            &error_abort),
+                    ==, 3);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
+                    ==, 2);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
+                                            QAUTHZ_LIST_POLICY_DENY,
+                                            QAUTHZ_LIST_FORMAT_EXACT,
+                                            2,
+                                            &error_abort),
+                    ==, 2);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+int main(int argc, char **argv)
+{
+    g_test_init(&argc, &argv, NULL);
+
+    module_call_init(MODULE_INIT_QOM);
+
+    g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
+    g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
+    g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
+    g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
+    g_test_add_func("/auth/list/complex", test_authz_complex);
+    g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
+
+    return g_test_run();
+}
diff --git a/.gitignore b/.gitignore
index 64efdfd929..9b9fcad829 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,6 +47,7 @@
 /qapi/qapi-commands-trace.[ch]
 /qapi/qapi-commands-transaction.[ch]
 /qapi/qapi-commands-ui.[ch]
+/qapi/qapi-commands-authz.[ch]
 /qapi/qapi-commands.[ch]
 /qapi/qapi-events-block-core.[ch]
 /qapi/qapi-events-block.[ch]
@@ -65,6 +66,7 @@
 /qapi/qapi-events-trace.[ch]
 /qapi/qapi-events-transaction.[ch]
 /qapi/qapi-events-ui.[ch]
+/qapi/qapi-events-authz.[ch]
 /qapi/qapi-events.[ch]
 /qapi/qapi-introspect.[ch]
 /qapi/qapi-types-block-core.[ch]
@@ -84,6 +86,7 @@
 /qapi/qapi-types-trace.[ch]
 /qapi/qapi-types-transaction.[ch]
 /qapi/qapi-types-ui.[ch]
+/qapi/qapi-types-authz.[ch]
 /qapi/qapi-types.[ch]
 /qapi/qapi-visit-block-core.[ch]
 /qapi/qapi-visit-block.[ch]
@@ -102,6 +105,7 @@
 /qapi/qapi-visit-trace.[ch]
 /qapi/qapi-visit-transaction.[ch]
 /qapi/qapi-visit-ui.[ch]
+/qapi/qapi-visit-authz.[ch]
 /qapi/qapi-visit.[ch]
 /qapi/qapi-doc.texi
 /qemu-doc.html
diff --git a/MAINTAINERS b/MAINTAINERS
index 9624734923..5d33cf4605 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -1861,6 +1861,7 @@ User authorization
 M: Daniel P. Berrange <berrange@redhat.com>
 S: Maintained
 F: authz/
+F: qapi/authz.json
 F: include/authz/
 F: tests/test-authz-*
 
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
index 2a75d53840..921fa624d7 100644
--- a/authz/Makefile.objs
+++ b/authz/Makefile.objs
@@ -1,2 +1,3 @@
 authz-obj-y += base.o
 authz-obj-y += simple.o
+authz-obj-y += list.o
diff --git a/authz/trace-events b/authz/trace-events
index 1ef796c1e1..a896d876e8 100644
--- a/authz/trace-events
+++ b/authz/trace-events
@@ -5,3 +5,7 @@ qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p che
 
 # auth/simple.c
 qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
+
+# auth/list.c
+qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
+qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
diff --git a/tests/Makefile.include b/tests/Makefile.include
index 7fe8578972..b2369c14cb 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -126,6 +126,7 @@ check-unit-y += tests/test-bufferiszero$(EXESUF)
 check-unit-y += tests/test-uuid$(EXESUF)
 check-unit-y += tests/ptimer-test$(EXESUF)
 check-unit-y += tests/test-qapi-util$(EXESUF)
+check-unit-y += tests/test-authz-list$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -572,6 +573,9 @@ tests/test-timed-average$(EXESUF): tests/test-timed-average.o $(test-util-obj-y)
 tests/test-base64$(EXESUF): tests/test-base64.o $(test-util-obj-y)
 tests/ptimer-test$(EXESUF): tests/ptimer-test.o tests/ptimer-test-stubs.o hw/core/ptimer.o
 
+tests/test-authz-list$(EXESUF): tests/test-authz-list.o \
+	$(test-util-obj-y) $(authz-obj-y) $(qom-obj-y)
+
 tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
 
 tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file access control list
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (7 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-10-22 23:56   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM Daniel P. Berrangé
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation Daniel P. Berrangé
  10 siblings, 2 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

Add a QAuthZListFile object type that implements the QAuthZ interface. This
built-in implementation is a proxy around the QAtuhZList object type,
initializing it from an external file, and optionally, automatically
reloading it whenever it changes.

To create an instance of this object via the QMP monitor, the syntax
used would be:

      {
        "execute": "object-add",
        "arguments": {
          "qom-type": "authz-list-file",
          "id": "authz0",
          "parameters": {
            "filename": "/etc/qemu/vnc.acl",
	    "refresh": "yes"
          }
        }
      }

If "refresh" is "yes", inotify is used to monitor the file,
automatically reloading changes. If an error occurs during reloading,
all authorizations will fail until the file is next successfully
loaded.

The /etc/qemu/vnc.acl file would contain a JSON representation of a
QAuthZList object

    {
      "rules": [
         { "match": "fred", "policy": "allow", "format": "exact" },
         { "match": "bob", "policy": "allow", "format": "exact" },
         { "match": "danb", "policy": "deny", "format": "glob" },
         { "match": "dan*", "policy": "allow", "format": "exact" },
      ],
      "policy": "deny"
    }

This sets up an authorization rule that allows 'fred', 'bob' and anyone
whose name starts with 'dan', except for 'danb'. Everyone unmatched is
denied.

The object can be loaded on the comand line using

   -object authz-list-file,id=authz0,filename=/etc/qemu/vnc.acl,refresh=yes

Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
---
 include/authz/listfile.h | 110 +++++++++++++++
 authz/listfile.c         | 286 +++++++++++++++++++++++++++++++++++++++
 authz/Makefile.objs      |   1 +
 authz/trace-events       |   4 +
 qemu-options.hx          |  46 +++++++
 5 files changed, 447 insertions(+)
 create mode 100644 include/authz/listfile.h
 create mode 100644 authz/listfile.c

diff --git a/include/authz/listfile.h b/include/authz/listfile.h
new file mode 100644
index 0000000000..244aadc064
--- /dev/null
+++ b/include/authz/listfile.h
@@ -0,0 +1,110 @@
+/*
+ * QEMU list file authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_LIST_FILE_H__
+#define QAUTHZ_LIST_FILE_H__
+
+#include "authz/list.h"
+#include "qapi/qapi-types-authz.h"
+#include "qemu/filemonitor.h"
+
+#define TYPE_QAUTHZ_LIST_FILE "authz-list-file"
+
+#define QAUTHZ_LIST_FILE_CLASS(klass)                        \
+    OBJECT_CLASS_CHECK(QAuthZListFileClass, (klass),        \
+                       TYPE_QAUTHZ_LIST_FILE)
+#define QAUTHZ_LIST_FILE_GET_CLASS(obj)              \
+    OBJECT_GET_CLASS(QAuthZListFileClass, (obj),    \
+                      TYPE_QAUTHZ_LIST_FILE)
+#define QAUTHZ_LIST_FILE(obj) \
+    INTERFACE_CHECK(QAuthZListFile, (obj),          \
+                    TYPE_QAUTHZ_LIST_FILE)
+
+typedef struct QAuthZListFile QAuthZListFile;
+typedef struct QAuthZListFileClass QAuthZListFileClass;
+
+
+/**
+ * QAuthZListFile:
+ *
+ * This authorization driver provides a file mechanism
+ * for granting access by matching user names against a
+ * file of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ *  {
+ *    "execute": "object-add",
+ *    "arguments": {
+ *      "qom-type": "authz-list-file",
+ *      "id": "authz0",
+ *      "parameters": {
+ *        "filename": "/etc/qemu/myvm-vnc.acl",
+ *        "refresh": "yes"
+ *      }
+ *    }
+ *  }
+ *
+ * If 'refresh' is 'yes', inotify is used to monitor for changes
+ * to the file and auto-reload the rules.
+ *
+ * The myvm-vnc.acl file should contain the parameters for
+ * the QAuthZList object in JSON format:
+ *
+ *      {
+ *        "rules": [
+ *           { "match": "fred", "policy": "allow", "format": "exact" },
+ *           { "match": "bob", "policy": "allow", "format": "exact" },
+ *           { "match": "danb", "policy": "deny", "format": "exact" },
+ *           { "match": "dan*", "policy": "allow", "format": "glob" }
+ *        ],
+ *        "policy": "deny"
+ *      }
+ *
+ * The object can be created on the command line using
+ *
+ *   -object authz-list-file,id=authz0,\
+ *           filename=/etc/qemu/myvm-vnc.acl,refresh=yes
+ *
+ */
+struct QAuthZListFile {
+    QAuthZ parent_obj;
+
+    QAuthZ *list;
+    char *filename;
+    bool refresh;
+    QFileMonitor *file_monitor;
+    int file_watch;
+};
+
+
+struct QAuthZListFileClass {
+    QAuthZClass parent_class;
+};
+
+
+QAuthZListFile *qauthz_list_file_new(const char *id,
+                                     const char *filename,
+                                     Error **errp);
+
+
+#endif /* QAUTHZ_LIST_FILE_H__ */
+
diff --git a/authz/listfile.c b/authz/listfile.c
new file mode 100644
index 0000000000..784bec2b1d
--- /dev/null
+++ b/authz/listfile.c
@@ -0,0 +1,286 @@
+/*
+ * QEMU access control list file authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/listfile.h"
+#include "authz/trace.h"
+#include "qemu/error-report.h"
+#include "qemu/main-loop.h"
+#include "qemu/sockets.h"
+#include "qemu/filemonitor.h"
+#include "qom/object_interfaces.h"
+#include "qapi/qapi-visit-authz.h"
+#include "qapi/qmp/qjson.h"
+#include "qapi/qmp/qobject.h"
+#include "qapi/qmp/qerror.h"
+#include "qapi/qobject-input-visitor.h"
+
+
+static bool
+qauthz_list_file_is_allowed(QAuthZ *authz,
+                            const char *identity,
+                            Error **errp)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(authz);
+    if (fauthz->list) {
+        return qauthz_is_allowed(fauthz->list, identity, errp);
+    }
+
+    return false;
+}
+
+
+static QAuthZ *
+qauthz_list_file_load(QAuthZListFile *fauthz, Error **errp)
+{
+    GError *err = NULL;
+    gchar *content = NULL;
+    gsize len;
+    QObject *obj = NULL;
+    QDict *pdict;
+    Visitor *v = NULL;
+    QAuthZ *ret = NULL;
+
+    trace_qauthz_list_file_load(fauthz, fauthz->filename);
+    if (!g_file_get_contents(fauthz->filename, &content, &len, &err)) {
+        error_setg(errp, "Unable to read '%s': %s",
+                   fauthz->filename, err->message);
+        goto cleanup;
+    }
+
+    obj = qobject_from_json(content, errp);
+    if (!obj) {
+        goto cleanup;
+    }
+
+    pdict = qobject_to(QDict, obj);
+    if (!pdict) {
+        error_setg(errp, QERR_INVALID_PARAMETER_TYPE, "obj", "dict");
+        goto cleanup;
+    }
+
+    v = qobject_input_visitor_new(obj);
+
+    ret = (QAuthZ *)user_creatable_add_type(TYPE_QAUTHZ_LIST,
+                                            NULL, pdict, v, errp);
+
+ cleanup:
+    visit_free(v);
+    qobject_unref(obj);
+    if (err) {
+        g_error_free(err);
+    }
+    g_free(content);
+    return ret;
+}
+
+
+static void
+qauthz_list_file_event(int wd G_GNUC_UNUSED,
+                       QFileMonitorEvent ev G_GNUC_UNUSED,
+                       const char *name G_GNUC_UNUSED,
+                       void *opaque)
+{
+    QAuthZListFile *fauthz = opaque;
+    Error *err = NULL;
+
+    if (ev != QFILE_MONITOR_EVENT_MODIFIED &&
+        ev != QFILE_MONITOR_EVENT_CREATED) {
+        return;
+    }
+
+    object_unref(OBJECT(fauthz->list));
+    fauthz->list = qauthz_list_file_load(fauthz, &err);
+    trace_qauthz_list_file_refresh(fauthz,
+                                   fauthz->filename, fauthz->list ? 1 : 0);
+    if (!fauthz->list) {
+        error_report_err(err);
+    }
+}
+
+static void
+qauthz_list_file_complete(UserCreatable *uc, Error **errp)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(uc);
+    gchar *dir = NULL, *file = NULL;
+
+    fauthz->list = qauthz_list_file_load(fauthz, errp);
+
+    if (!fauthz->refresh) {
+        return;
+    }
+
+    fauthz->file_monitor = qemu_file_monitor_get_instance(errp);
+    if (!fauthz->file_monitor) {
+        return;
+    }
+
+    dir = g_path_get_dirname(fauthz->filename);
+    if (g_str_equal(dir, ".")) {
+        error_setg(errp, "Filename must be an absolute path");
+        goto cleanup;
+    }
+    file = g_path_get_basename(fauthz->filename);
+    if (g_str_equal(file, ".")) {
+        error_setg(errp, "Path has no trailing filename component");
+        goto cleanup;
+    }
+
+    fauthz->file_watch = qemu_file_monitor_add_watch(
+        fauthz->file_monitor, dir, file,
+        qauthz_list_file_event, fauthz, errp);
+    if (fauthz->file_watch < 0) {
+        goto cleanup;
+    }
+
+ cleanup:
+    g_free(file);
+    g_free(dir);
+}
+
+
+static void
+qauthz_list_file_prop_set_filename(Object *obj,
+                                   const char *value,
+                                   Error **errp G_GNUC_UNUSED)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+    fauthz->filename = g_strdup(value);
+}
+
+
+static char *
+qauthz_list_file_prop_get_filename(Object *obj,
+                                   Error **errp G_GNUC_UNUSED)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+    return g_strdup(fauthz->filename);
+}
+
+
+static void
+qauthz_list_file_prop_set_refresh(Object *obj,
+                                  bool value,
+                                  Error **errp G_GNUC_UNUSED)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+    fauthz->refresh = value;
+}
+
+
+static bool
+qauthz_list_file_prop_get_refresh(Object *obj,
+                                  Error **errp G_GNUC_UNUSED)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+    return fauthz->refresh;
+}
+
+
+static void
+qauthz_list_file_finalize(Object *obj)
+{
+    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
+
+    if (fauthz->file_watch != -1 && fauthz->file_monitor) {
+        gchar *dir = g_path_get_dirname(fauthz->filename);
+        qemu_file_monitor_remove_watch(fauthz->file_monitor,
+                                       dir,
+                                       fauthz->file_watch);
+        g_free(dir);
+    }
+    object_unref(OBJECT(fauthz->list));
+    g_free(fauthz->filename);
+}
+
+
+static void
+qauthz_list_file_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+    ucc->complete = qauthz_list_file_complete;
+
+    object_class_property_add_str(oc, "filename",
+                                  qauthz_list_file_prop_get_filename,
+                                  qauthz_list_file_prop_set_filename,
+                                  NULL);
+    object_class_property_add_bool(oc, "refresh",
+                                   qauthz_list_file_prop_get_refresh,
+                                   qauthz_list_file_prop_set_refresh,
+                                   NULL);
+
+    authz->is_allowed = qauthz_list_file_is_allowed;
+}
+
+
+static void
+qauthz_list_file_init(Object *obj)
+{
+    QAuthZListFile *authz = QAUTHZ_LIST_FILE(obj);
+
+    authz->file_watch = -1;
+#ifdef CONFIG_INOTIFY1
+    authz->refresh = TRUE;
+#endif
+}
+
+
+QAuthZListFile *qauthz_list_file_new(const char *id,
+                                     const char *filename,
+                                     Error **errp)
+{
+    return QAUTHZ_LIST_FILE(
+        object_new_with_props(TYPE_QAUTHZ_LIST_FILE,
+                              object_get_objects_root(),
+                              id, errp,
+                              "filename", filename,
+                              NULL));
+}
+
+
+static const TypeInfo qauthz_list_file_info = {
+    .parent = TYPE_QAUTHZ_LIST,
+    .name = TYPE_QAUTHZ_LIST_FILE,
+    .instance_init = qauthz_list_file_init,
+    .instance_size = sizeof(QAuthZListFile),
+    .instance_finalize = qauthz_list_file_finalize,
+    .class_size = sizeof(QAuthZListFileClass),
+    .class_init = qauthz_list_file_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+
+static void
+qauthz_list_file_register_types(void)
+{
+    type_register_static(&qauthz_list_file_info);
+}
+
+
+type_init(qauthz_list_file_register_types);
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
index 921fa624d7..8351bf181d 100644
--- a/authz/Makefile.objs
+++ b/authz/Makefile.objs
@@ -1,3 +1,4 @@
 authz-obj-y += base.o
 authz-obj-y += simple.o
 authz-obj-y += list.o
+authz-obj-y += listfile.o
diff --git a/authz/trace-events b/authz/trace-events
index a896d876e8..fb65349a90 100644
--- a/authz/trace-events
+++ b/authz/trace-events
@@ -9,3 +9,7 @@ qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *goti
 # auth/list.c
 qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
 qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
+
+# auth/listfile.c
+qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
+qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
diff --git a/qemu-options.hx b/qemu-options.hx
index 68eaf39cc4..a1c3e0e59c 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4401,6 +4401,52 @@ would look like:
 Note the use of quotes due to the x509 distinguished name containing
 whitespace, and escaping of ','.
 
+@item -object authz-listfile,id=@var{id},filename=@var{path},refresh=@var{yes|no}
+
+Create an authorization object that will control access to network services.
+
+The @option{filename} parameter is the fully qualified path to a file
+containing the access control list rules in JSON format.
+
+An example set of rules that match against SASL usernames might look
+like:
+
+@example
+  @{
+    "rules": [
+       @{ "match": "fred", "policy": "allow", "format": "exact" @},
+       @{ "match": "bob", "policy": "allow", "format": "exact" @},
+       @{ "match": "danb", "policy": "deny", "format": "glob" @},
+       @{ "match": "dan*", "policy": "allow", "format": "exact" @},
+    ],
+    "policy": "deny"
+  @}
+@end example
+
+When checking access the object will iterate over all the rules and
+the first rule to match will have its @option{policy} value returned
+as the result. If no rules match, then the default @option{policy}
+value is returned.
+
+The rules can either be an exact string match, or they can use the
+simple UNIX glob pattern matching to allow wildcards to be used.
+
+If @option{refresh} is set to true the file will be monitored
+and automatically reloaded whenever its content changes.
+
+As with the @code{authz-simple} object, the format of the identity
+strings being matched depends on the network service, but is usually
+a TLS x509 distinguished name, or a SASL username.
+
+An example authorization object to validate a SASL username
+would look like:
+@example
+ # $QEMU \
+     ...
+     -object authz-simple,id=auth0,filename=/etc/qemu/vnc-sasl.acl,refresh=yes
+     ...
+@end example
+
 @end table
 
 ETEXI
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (8 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file " Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-11-07 22:23   ` Marc-André Lureau
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation Daniel P. Berrangé
  10 siblings, 1 reply; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

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

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.

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 configure               |  37 ++++++++++
 include/authz/pamacct.h | 100 +++++++++++++++++++++++++++
 authz/pamacct.c         | 149 ++++++++++++++++++++++++++++++++++++++++
 authz/Makefile.objs     |   3 +
 authz/trace-events      |   3 +
 qemu-options.hx         |  35 ++++++++++
 6 files changed, 327 insertions(+)
 create mode 100644 include/authz/pamacct.h
 create mode 100644 authz/pamacct.c

diff --git a/configure b/configure
index 9138af37f8..db35d52e12 100755
--- a/configure
+++ b/configure
@@ -463,6 +463,7 @@ nettle_kdf="no"
 gcrypt=""
 gcrypt_hmac="no"
 gcrypt_kdf="no"
+auth_pam=""
 vte=""
 virglrenderer=""
 tpm="yes"
@@ -1361,6 +1362,10 @@ for opt do
   ;;
   --enable-gcrypt) gcrypt="yes"
   ;;
+  --disable-auth-pam) auth_pam="no"
+  ;;
+  --enable-auth-pam) auth_pam="yes"
+  ;;
   --enable-rdma) rdma="yes"
   ;;
   --disable-rdma) rdma="no"
@@ -1653,6 +1658,7 @@ disabled with --disable-FEATURE, default is enabled if available:
   gnutls          GNUTLS cryptography support
   nettle          nettle cryptography support
   gcrypt          libgcrypt cryptography support
+  auth-pam        PAM access control
   sdl             SDL UI
   --with-sdlabi     select preferred SDL ABI 1.2 or 2.0
   gtk             gtk UI
@@ -2876,6 +2882,33 @@ else
 fi
 
 
+##########################################
+# PAM probe
+
+if test "x$auth_pam" != "no"; then
+    cat > $TMPC <<EOF
+#include <security/pam_appl.h>
+#include <stdio.h>
+int main(void) {
+   const char *service_name = "qemu";
+   const char *user = "frank";
+   const struct pam_conv *pam_conv = NULL;
+   pam_handle_t *pamh = NULL;
+   pam_start(service_name, user, pam_conv, &pamh);
+   return 0;
+}
+EOF
+    if compile_prog "" "-lpam" ; then
+	auth_pam=yes
+    else
+	if test "$auth_pam" = "yes"; then
+	    feature_not_found "PAM" "Install PAM development package"
+	else
+	    auth_pam=no
+	fi
+    fi
+fi
+
 ##########################################
 # getifaddrs (for tests/test-io-channel-socket )
 
@@ -5967,6 +6000,7 @@ echo "libgcrypt kdf     $gcrypt_kdf"
 echo "nettle            $nettle $(echo_version $nettle $nettle_version)"
 echo "nettle kdf        $nettle_kdf"
 echo "libtasn1          $tasn1"
+echo "PAM               $auth_pam"
 echo "curses support    $curses"
 echo "virgl support     $virglrenderer $(echo_version $virglrenderer $virgl_version)"
 echo "curl support      $curl"
@@ -6423,6 +6457,9 @@ fi
 if test "$tasn1" = "yes" ; then
   echo "CONFIG_TASN1=y" >> $config_host_mak
 fi
+if test "$auth_pam" = "yes" ; then
+    echo "CONFIG_AUTH_PAM=y" >> $config_host_mak
+fi
 if test "$have_ifaddrs_h" = "yes" ; then
     echo "HAVE_IFADDRS_H=y" >> $config_host_mak
 fi
diff --git a/include/authz/pamacct.h b/include/authz/pamacct.h
new file mode 100644
index 0000000000..d2c0149153
--- /dev/null
+++ b/include/authz/pamacct.h
@@ -0,0 +1,100 @@
+/*
+ * QEMU PAM authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#ifndef QAUTHZ_PAM_H__
+#define QAUTHZ_PAM_H__
+
+#include "authz/base.h"
+
+
+#define TYPE_QAUTHZ_PAM "authz-pam"
+
+#define QAUTHZ_PAM_CLASS(klass) \
+     OBJECT_CLASS_CHECK(QAuthZPamClass, (klass), \
+                        TYPE_QAUTHZ_PAM)
+#define QAUTHZ_PAM_GET_CLASS(obj) \
+     OBJECT_GET_CLASS(QAuthZPamClass, (obj), \
+                      TYPE_QAUTHZ_PAM)
+#define QAUTHZ_PAM(obj) \
+     INTERFACE_CHECK(QAuthZPam, (obj), \
+                     TYPE_QAUTHZ_PAM)
+
+typedef struct QAuthZPam QAuthZPam;
+typedef struct QAuthZPamClass QAuthZPamClass;
+
+
+/**
+ * QAuthZPam:
+ *
+ * This authorization driver provides a PAM mechanism
+ * for granting access by matching user names against a
+ * list of globs. Each match rule has an associated policy
+ * and a catch all policy applies if no rule matches
+ *
+ * To create an instance of this class via QMP:
+ *
+ *  {
+ *    "execute": "object-add",
+ *    "arguments": {
+ *      "qom-type": "authz-pam",
+ *      "id": "authz0",
+ *      "parameters": {
+ *        "service": "qemu-vnc-tls"
+ *      }
+ *    }
+ *  }
+ *
+ * The driver only uses the PAM "account" verification
+ * subsystem. The above config would require a config
+ * file /etc/pam.d/qemu-vnc-tls. For a simple file
+ * lookup it would contain
+ *
+ *   account requisite  pam_listfile.so item=user sense=allow \
+ *           file=/etc/qemu/vnc.allow
+ *
+ * The external file would then contain a list of usernames.
+ * If x509 cert was being used as the username, a suitable
+ * entry would match the distinguish name:
+ *
+ *  CN=laptop.berrange.com,O=Berrange Home,L=London,ST=London,C=GB
+ *
+ * On the command line it can be created using
+ *
+ *   -object authz-pam,id=authz0,service=qemu-vnc-tls
+ *
+ */
+struct QAuthZPam {
+    QAuthZ parent_obj;
+
+    char *service;
+};
+
+
+struct QAuthZPamClass {
+    QAuthZClass parent_class;
+};
+
+
+QAuthZPam *qauthz_pam_new(const char *id,
+                          const char *service,
+                          Error **errp);
+
+
+#endif /* QAUTHZ_PAM_H__ */
diff --git a/authz/pamacct.c b/authz/pamacct.c
new file mode 100644
index 0000000000..a070dda217
--- /dev/null
+++ b/authz/pamacct.c
@@ -0,0 +1,149 @@
+/*
+ * QEMU PAM authorization driver
+ *
+ * Copyright (c) 2018 Red Hat, Inc.
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+#include "qemu/osdep.h"
+#include "authz/pamacct.h"
+#include "authz/trace.h"
+#include "qom/object_interfaces.h"
+
+#include <security/pam_appl.h>
+
+
+static bool qauthz_pam_is_allowed(QAuthZ *authz,
+                                  const char *identity,
+                                  Error **errp)
+{
+    QAuthZPam *pauthz = QAUTHZ_PAM(authz);
+    const struct pam_conv pam_conversation = { 0 };
+    pam_handle_t *pamh = NULL;
+    int ret;
+
+    trace_qauthz_pam_check(authz, identity, pauthz->service);
+    ret = pam_start(pauthz->service,
+                    identity,
+                    &pam_conversation,
+                    &pamh);
+    if (ret != PAM_SUCCESS) {
+        error_setg(errp, "Unable to start PAM transaction: %s",
+                   pam_strerror(NULL, ret));
+        return false;
+    }
+
+    ret = pam_acct_mgmt(pamh, PAM_SILENT);
+    if (ret != PAM_SUCCESS) {
+        error_setg(errp, "Unable to authorize user '%s': %s",
+                   identity, pam_strerror(pamh, ret));
+        goto cleanup;
+    }
+
+ cleanup:
+    pam_end(pamh, ret);
+    return ret == PAM_SUCCESS;
+}
+
+
+static void
+qauthz_pam_prop_set_service(Object *obj,
+                            const char *service,
+                            Error **errp G_GNUC_UNUSED)
+{
+    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
+
+    g_free(pauthz->service);
+    pauthz->service = g_strdup(service);
+}
+
+
+static char *
+qauthz_pam_prop_get_service(Object *obj,
+                            Error **errp G_GNUC_UNUSED)
+{
+    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
+
+    return g_strdup(pauthz->service);
+}
+
+
+static void
+qauthz_pam_complete(UserCreatable *uc, Error **errp)
+{
+}
+
+
+static void
+qauthz_pam_finalize(Object *obj)
+{
+    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
+
+    g_free(pauthz->service);
+}
+
+
+static void
+qauthz_pam_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+    ucc->complete = qauthz_pam_complete;
+    authz->is_allowed = qauthz_pam_is_allowed;
+
+    object_class_property_add_str(oc, "service",
+                                  qauthz_pam_prop_get_service,
+                                  qauthz_pam_prop_set_service,
+                                  NULL);
+}
+
+
+QAuthZPam *qauthz_pam_new(const char *id,
+                          const char *service,
+                          Error **errp)
+{
+    return QAUTHZ_PAM(
+        object_new_with_props(TYPE_QAUTHZ_PAM,
+                              object_get_objects_root(),
+                              id, errp,
+                              "service", service,
+                              NULL));
+}
+
+
+static const TypeInfo qauthz_pam_info = {
+    .parent = TYPE_QAUTHZ,
+    .name = TYPE_QAUTHZ_PAM,
+    .instance_size = sizeof(QAuthZPam),
+    .instance_finalize = qauthz_pam_finalize,
+    .class_size = sizeof(QAuthZPamClass),
+    .class_init = qauthz_pam_class_init,
+    .interfaces = (InterfaceInfo[]) {
+        { TYPE_USER_CREATABLE },
+        { }
+    }
+};
+
+
+static void
+qauthz_pam_register_types(void)
+{
+    type_register_static(&qauthz_pam_info);
+}
+
+
+type_init(qauthz_pam_register_types);
diff --git a/authz/Makefile.objs b/authz/Makefile.objs
index 8351bf181d..ed7b273596 100644
--- a/authz/Makefile.objs
+++ b/authz/Makefile.objs
@@ -2,3 +2,6 @@ authz-obj-y += base.o
 authz-obj-y += simple.o
 authz-obj-y += list.o
 authz-obj-y += listfile.o
+authz-obj-$(CONFIG_AUTH_PAM) += pamacct.o
+
+pamacct.o-libs = -lpam
diff --git a/authz/trace-events b/authz/trace-events
index fb65349a90..72c411927d 100644
--- a/authz/trace-events
+++ b/authz/trace-events
@@ -13,3 +13,6 @@ qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ
 # auth/listfile.c
 qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
 qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
+
+# auth/pam.c
+qauthz_pam_check(void *authz, const char *identity, const char *service) "AuthZ PAM %p identity=%s service=%s"
diff --git a/qemu-options.hx b/qemu-options.hx
index a1c3e0e59c..a9654b8115 100644
--- a/qemu-options.hx
+++ b/qemu-options.hx
@@ -4447,6 +4447,41 @@ would look like:
      ...
 @end example
 
+@item -object authz-pam,id=@var{id},service=@var{string}
+
+Create an authorization object that will control access to network services.
+
+The @option{service} parameter provides the name of a PAM service to use
+for authorization. It requires that a file @code{/etc/pam.d/@var{service}}
+exist to provide the configuration for the @code{account} subsystem.
+
+An example authorization object to validate a TLS x509 distinguished
+name would look like:
+
+@example
+ # $QEMU \
+     ...
+     -object authz-simple,id=auth0,service=qemu-vnc
+     ...
+@end example
+
+There would then be a corresponding config file for PAM at
+@code{/etc/pam.d/qemu-vnc} that contains:
+
+@example
+account requisite  pam_listfile.so item=user sense=allow \
+           file=/etc/qemu/vnc.allow
+@end example
+
+Finally the @code{/etc/qemu/vnc.allow} file would contain
+the list of x509 distingished names that are permitted
+access
+
+@example
+CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
+@end example
+
+
 @end table
 
 ETEXI
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation
  2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
                   ` (9 preceding siblings ...)
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM Daniel P. Berrangé
@ 2018-10-19 13:38 ` Daniel P. Berrangé
  2018-10-23 11:14   ` Philippe Mathieu-Daudé
  2018-11-08  8:15   ` Marc-André Lureau
  10 siblings, 2 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-10-19 13:38 UTC (permalink / raw)
  To: qemu-devel
  Cc: Daniel P. Berrangé,
	Gerd Hoffmann, Dr. David Alan Gilbert,
	Philippe Mathieu-Daudé,
	Eric Blake, Markus Armbruster

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

The 'qemu_acl' type was a previous non-QOM based attempt to provide an
authorization facility in QEMU. Because it is non-QOM based it cannot be
created via the command line and requires special monitor commands to
manipulate it.

The new QAuthZ subclasses provide a superset of the functionality in
qemu_acl, so the latter can now be deleted. The HMP 'acl_*' monitor
commands are converted to use the new QAuthZSimple data type instead
in order to provide temporary backwards compatibility.

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 include/qemu/acl.h             |  66 ------------
 ui/vnc-auth-sasl.h             |   5 +-
 ui/vnc.h                       |   4 +-
 crypto/tlssession.c            |  35 +++----
 monitor.c                      | 185 ++++++++++++++++++++++-----------
 tests/test-crypto-tlssession.c |  15 ++-
 tests/test-io-channel-tls.c    |  16 ++-
 ui/vnc-auth-sasl.c             |  23 ++--
 ui/vnc-auth-vencrypt.c         |   2 +-
 ui/vnc-ws.c                    |   2 +-
 ui/vnc.c                       |  37 ++++---
 util/acl.c                     | 179 -------------------------------
 crypto/trace-events            |   2 +-
 tests/Makefile.include         |   4 +-
 util/Makefile.objs             |   1 -
 15 files changed, 215 insertions(+), 361 deletions(-)
 delete mode 100644 include/qemu/acl.h
 delete mode 100644 util/acl.c

diff --git a/include/qemu/acl.h b/include/qemu/acl.h
deleted file mode 100644
index 7c44119a47..0000000000
--- a/include/qemu/acl.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * QEMU access control list management
- *
- * Copyright (C) 2009 Red Hat, Inc
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-#ifndef QEMU_ACL_H
-#define QEMU_ACL_H
-
-#include "qemu/queue.h"
-
-typedef struct qemu_acl_entry qemu_acl_entry;
-typedef struct qemu_acl qemu_acl;
-
-struct qemu_acl_entry {
-    char *match;
-    int deny;
-
-    QTAILQ_ENTRY(qemu_acl_entry) next;
-};
-
-struct qemu_acl {
-    char *aclname;
-    unsigned int nentries;
-    QTAILQ_HEAD(,qemu_acl_entry) entries;
-    int defaultDeny;
-};
-
-qemu_acl *qemu_acl_init(const char *aclname);
-
-qemu_acl *qemu_acl_find(const char *aclname);
-
-int qemu_acl_party_is_allowed(qemu_acl *acl,
-			      const char *party);
-
-void qemu_acl_reset(qemu_acl *acl);
-
-int qemu_acl_append(qemu_acl *acl,
-		    int deny,
-		    const char *match);
-int qemu_acl_insert(qemu_acl *acl,
-		    int deny,
-		    const char *match,
-		    int index);
-int qemu_acl_remove(qemu_acl *acl,
-		    const char *match);
-
-#endif /* QEMU_ACL_H */
diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h
index 2ae224ee3a..fb55fe04ca 100644
--- a/ui/vnc-auth-sasl.h
+++ b/ui/vnc-auth-sasl.h
@@ -30,8 +30,8 @@
 typedef struct VncStateSASL VncStateSASL;
 typedef struct VncDisplaySASL VncDisplaySASL;
 
-#include "qemu/acl.h"
 #include "qemu/main-loop.h"
+#include "authz/base.h"
 
 struct VncStateSASL {
     sasl_conn_t *conn;
@@ -60,7 +60,8 @@ struct VncStateSASL {
 };
 
 struct VncDisplaySASL {
-    qemu_acl *acl;
+    QAuthZ *authz;
+    char *authzid;
 };
 
 void vnc_sasl_client_cleanup(VncState *vs);
diff --git a/ui/vnc.h b/ui/vnc.h
index a86e0610e8..29ee1738a5 100644
--- a/ui/vnc.h
+++ b/ui/vnc.h
@@ -39,6 +39,7 @@
 #include "io/channel-socket.h"
 #include "io/channel-tls.h"
 #include "io/net-listener.h"
+#include "authz/base.h"
 #include <zlib.h>
 
 #include "keymaps.h"
@@ -177,7 +178,8 @@ struct VncDisplay
     bool lossy;
     bool non_adaptive;
     QCryptoTLSCreds *tlscreds;
-    char *tlsaclname;
+    QAuthZ *tlsauthz;
+    char *tlsauthzid;
 #ifdef CONFIG_VNC_SASL
     VncDisplaySASL sasl;
 #endif
diff --git a/crypto/tlssession.c b/crypto/tlssession.c
index 66a6fbe19c..23842aa95d 100644
--- a/crypto/tlssession.c
+++ b/crypto/tlssession.c
@@ -24,7 +24,7 @@
 #include "crypto/tlscredspsk.h"
 #include "crypto/tlscredsx509.h"
 #include "qapi/error.h"
-#include "qemu/acl.h"
+#include "authz/base.h"
 #include "trace.h"
 
 #ifdef CONFIG_GNUTLS
@@ -37,7 +37,7 @@ struct QCryptoTLSSession {
     QCryptoTLSCreds *creds;
     gnutls_session_t handle;
     char *hostname;
-    char *aclname;
+    char *authzid;
     bool handshakeComplete;
     QCryptoTLSSessionWriteFunc writeFunc;
     QCryptoTLSSessionReadFunc readFunc;
@@ -56,7 +56,7 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
     gnutls_deinit(session->handle);
     g_free(session->hostname);
     g_free(session->peername);
-    g_free(session->aclname);
+    g_free(session->authzid);
     object_unref(OBJECT(session->creds));
     g_free(session);
 }
@@ -101,7 +101,7 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
 QCryptoTLSSession *
 qcrypto_tls_session_new(QCryptoTLSCreds *creds,
                         const char *hostname,
-                        const char *aclname,
+                        const char *authzid,
                         QCryptoTLSCredsEndpoint endpoint,
                         Error **errp)
 {
@@ -111,13 +111,13 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds,
     session = g_new0(QCryptoTLSSession, 1);
     trace_qcrypto_tls_session_new(
         session, creds, hostname ? hostname : "<none>",
-        aclname ? aclname : "<none>", endpoint);
+        authzid ? authzid : "<none>", endpoint);
 
     if (hostname) {
         session->hostname = g_strdup(hostname);
     }
-    if (aclname) {
-        session->aclname = g_strdup(aclname);
+    if (authzid) {
+        session->authzid = g_strdup(authzid);
     }
     session->creds = creds;
     object_ref(OBJECT(creds));
@@ -268,6 +268,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
     unsigned int nCerts, i;
     time_t now;
     gnutls_x509_crt_t cert = NULL;
+    Error *err = NULL;
 
     now = time(NULL);
     if (now == ((time_t)-1)) {
@@ -355,19 +356,17 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
                            gnutls_strerror(ret));
                 goto error;
             }
-            if (session->aclname) {
-                qemu_acl *acl = qemu_acl_find(session->aclname);
-                int allow;
-                if (!acl) {
-                    error_setg(errp, "Cannot find ACL %s",
-                               session->aclname);
+            if (session->authzid) {
+                bool allow;
+
+                allow = qauthz_is_allowed_by_id(session->authzid,
+                                                session->peername, &err);
+                if (err) {
+                    error_propagate(errp, err);
                     goto error;
                 }
-
-                allow = qemu_acl_party_is_allowed(acl, session->peername);
-
                 if (!allow) {
-                    error_setg(errp, "TLS x509 ACL check for %s is denied",
+                    error_setg(errp, "TLS x509 authz check for %s is denied",
                                session->peername);
                     goto error;
                 }
@@ -558,7 +557,7 @@ qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
 QCryptoTLSSession *
 qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
                         const char *hostname G_GNUC_UNUSED,
-                        const char *aclname G_GNUC_UNUSED,
+                        const char *authzid G_GNUC_UNUSED,
                         QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
                         Error **errp)
 {
diff --git a/monitor.c b/monitor.c
index b9258a7438..2458322eb1 100644
--- a/monitor.c
+++ b/monitor.c
@@ -51,7 +51,8 @@
 #include "sysemu/balloon.h"
 #include "qemu/timer.h"
 #include "sysemu/hw_accel.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
+#include "qapi/util.h"
 #include "sysemu/tpm.h"
 #include "qapi/qmp/qdict.h"
 #include "qapi/qmp/qerror.h"
@@ -2040,93 +2041,154 @@ static void hmp_wavcapture(Monitor *mon, const QDict *qdict)
     QLIST_INSERT_HEAD (&capture_head, s, entries);
 }
 
-static qemu_acl *find_acl(Monitor *mon, const char *name)
+static QAuthZList *find_auth(Monitor *mon, const char *name)
 {
-    qemu_acl *acl = qemu_acl_find(name);
+    Object *obj;
+    Object *container;
 
-    if (!acl) {
+    container = object_get_objects_root();
+    obj = object_resolve_path_component(container, name);
+    if (!obj) {
         monitor_printf(mon, "acl: unknown list '%s'\n", name);
+        return NULL;
     }
-    return acl;
+
+    return QAUTHZ_LIST(obj);
 }
 
 static void hmp_acl_show(Monitor *mon, const QDict *qdict)
 {
     const char *aclname = qdict_get_str(qdict, "aclname");
-    qemu_acl *acl = find_acl(mon, aclname);
-    qemu_acl_entry *entry;
-    int i = 0;
-
-    if (acl) {
-        monitor_printf(mon, "policy: %s\n",
-                       acl->defaultDeny ? "deny" : "allow");
-        QTAILQ_FOREACH(entry, &acl->entries, next) {
-            i++;
-            monitor_printf(mon, "%d: %s %s\n", i,
-                           entry->deny ? "deny" : "allow", entry->match);
-        }
+    QAuthZList *auth = find_auth(mon, aclname);
+    QAuthZListRuleList *rules;
+    size_t i = 0;
+
+    if (!auth) {
+        return;
+    }
+
+    monitor_printf(mon, "policy: %s\n",
+                   QAuthZListPolicy_lookup.array[auth->policy]);
+
+    rules = auth->rules;
+    while (rules) {
+        QAuthZListRule *rule = rules->value;
+        i++;
+        monitor_printf(mon, "%zu: %s %s\n", i,
+                       QAuthZListPolicy_lookup.array[rule->policy],
+                       rule->match);
+        rules = rules->next;
     }
 }
 
 static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
 {
     const char *aclname = qdict_get_str(qdict, "aclname");
-    qemu_acl *acl = find_acl(mon, aclname);
+    QAuthZList *auth = find_auth(mon, aclname);
 
-    if (acl) {
-        qemu_acl_reset(acl);
-        monitor_printf(mon, "acl: removed all rules\n");
+    if (!auth) {
+        return;
     }
+
+    auth->policy = QAUTHZ_LIST_POLICY_DENY;
+    qapi_free_QAuthZListRuleList(auth->rules);
+    auth->rules = NULL;
+    monitor_printf(mon, "acl: removed all rules\n");
 }
 
 static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
 {
     const char *aclname = qdict_get_str(qdict, "aclname");
     const char *policy = qdict_get_str(qdict, "policy");
-    qemu_acl *acl = find_acl(mon, aclname);
+    QAuthZList *auth = find_auth(mon, aclname);
+    int val;
+    Error *err = NULL;
+
+    if (!auth) {
+        return;
+    }
 
-    if (acl) {
-        if (strcmp(policy, "allow") == 0) {
-            acl->defaultDeny = 0;
+    val = qapi_enum_parse(&QAuthZListPolicy_lookup,
+                          policy,
+                          QAUTHZ_LIST_POLICY_DENY,
+                          &err);
+    if (err) {
+        error_free(err);
+        monitor_printf(mon, "acl: unknown policy '%s', "
+                       "expected 'deny' or 'allow'\n", policy);
+    } else {
+        auth->policy = val;
+        if (auth->policy == QAUTHZ_LIST_POLICY_ALLOW) {
             monitor_printf(mon, "acl: policy set to 'allow'\n");
-        } else if (strcmp(policy, "deny") == 0) {
-            acl->defaultDeny = 1;
-            monitor_printf(mon, "acl: policy set to 'deny'\n");
         } else {
-            monitor_printf(mon, "acl: unknown policy '%s', "
-                           "expected 'deny' or 'allow'\n", policy);
+            monitor_printf(mon, "acl: policy set to 'deny'\n");
         }
     }
 }
 
+static QAuthZListFormat hmp_acl_get_format(const char *match)
+{
+#ifdef CONFIG_FNMATCH
+    if (strchr(match, '*')) {
+        return QAUTHZ_LIST_FORMAT_GLOB;
+    } else {
+        return QAUTHZ_LIST_FORMAT_EXACT;
+    }
+#else
+    /* Historically we silently degraded to plain strcmp
+     * when fnmatch() was missing */
+    return QAUTHZ_LIST_FORMAT_EXACT;
+#endif
+}
+
 static void hmp_acl_add(Monitor *mon, const QDict *qdict)
 {
     const char *aclname = qdict_get_str(qdict, "aclname");
     const char *match = qdict_get_str(qdict, "match");
-    const char *policy = qdict_get_str(qdict, "policy");
+    const char *policystr = qdict_get_str(qdict, "policy");
     int has_index = qdict_haskey(qdict, "index");
     int index = qdict_get_try_int(qdict, "index", -1);
-    qemu_acl *acl = find_acl(mon, aclname);
-    int deny, ret;
-
-    if (acl) {
-        if (strcmp(policy, "allow") == 0) {
-            deny = 0;
-        } else if (strcmp(policy, "deny") == 0) {
-            deny = 1;
-        } else {
-            monitor_printf(mon, "acl: unknown policy '%s', "
-                           "expected 'deny' or 'allow'\n", policy);
-            return;
-        }
-        if (has_index)
-            ret = qemu_acl_insert(acl, deny, match, index);
-        else
-            ret = qemu_acl_append(acl, deny, match);
-        if (ret < 0)
-            monitor_printf(mon, "acl: unable to add acl entry\n");
-        else
-            monitor_printf(mon, "acl: added rule at position %d\n", ret);
+    QAuthZList *auth = find_auth(mon, aclname);
+    Error *err = NULL;
+    QAuthZListPolicy policy;
+    QAuthZListFormat format;
+    size_t i = 0;
+
+    if (!auth) {
+        return;
+    }
+
+    policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
+                             policystr,
+                             QAUTHZ_LIST_POLICY_DENY,
+                             &err);
+    if (err) {
+        error_free(err);
+        monitor_printf(mon, "acl: unknown policy '%s', "
+                       "expected 'deny' or 'allow'\n", policystr);
+        return;
+    }
+
+    format = hmp_acl_get_format(match);
+
+    if (has_index && index == 0) {
+        monitor_printf(mon, "acl: unable to add acl entry\n");
+        return;
+    }
+
+    if (has_index) {
+        i = qauthz_list_insert_rule(auth, match, policy,
+                                    format, index - 1, &err);
+    } else {
+        i = qauthz_list_append_rule(auth, match, policy,
+                                    format, &err);
+    }
+    if (err) {
+        monitor_printf(mon, "acl: unable to add rule: %s",
+                       error_get_pretty(err));
+        error_free(err);
+    } else {
+        monitor_printf(mon, "acl: added rule at position %zu\n", i + 1);
     }
 }
 
@@ -2134,15 +2196,18 @@ static void hmp_acl_remove(Monitor *mon, const QDict *qdict)
 {
     const char *aclname = qdict_get_str(qdict, "aclname");
     const char *match = qdict_get_str(qdict, "match");
-    qemu_acl *acl = find_acl(mon, aclname);
-    int ret;
+    QAuthZList *auth = find_auth(mon, aclname);
+    ssize_t i = 0;
 
-    if (acl) {
-        ret = qemu_acl_remove(acl, match);
-        if (ret < 0)
-            monitor_printf(mon, "acl: no matching acl entry\n");
-        else
-            monitor_printf(mon, "acl: removed rule at position %d\n", ret);
+    if (!auth) {
+        return;
+    }
+
+    i = qauthz_list_delete_rule(auth, match);
+    if (i >= 0) {
+        monitor_printf(mon, "acl: removed rule at position %zu\n", i + 1);
+    } else {
+        monitor_printf(mon, "acl: no matching acl entry\n");
     }
 }
 
diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c
index 6fa9950afb..15212ec276 100644
--- a/tests/test-crypto-tlssession.c
+++ b/tests/test-crypto-tlssession.c
@@ -28,7 +28,7 @@
 #include "qom/object_interfaces.h"
 #include "qapi/error.h"
 #include "qemu/sockets.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
 
 #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
 
@@ -229,7 +229,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
     QCryptoTLSCreds *serverCreds;
     QCryptoTLSSession *clientSess = NULL;
     QCryptoTLSSession *serverSess = NULL;
-    qemu_acl *acl;
+    QAuthZList *auth;
     const char * const *wildcards;
     int channel[2];
     bool clientShake = false;
@@ -285,11 +285,15 @@ static void test_crypto_tls_session_x509(const void *opaque)
         SERVER_CERT_DIR);
     g_assert(serverCreds != NULL);
 
-    acl = qemu_acl_init("tlssessionacl");
-    qemu_acl_reset(acl);
+    auth = qauthz_list_new("tlssessionacl",
+                           QAUTHZ_LIST_POLICY_DENY,
+                           &error_abort);
     wildcards = data->wildcards;
     while (wildcards && *wildcards) {
-        qemu_acl_append(acl, 0, *wildcards);
+        qauthz_list_append_rule(auth, *wildcards,
+                                QAUTHZ_LIST_POLICY_ALLOW,
+                                QAUTHZ_LIST_FORMAT_GLOB,
+                                &error_abort);
         wildcards++;
     }
 
@@ -377,6 +381,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
 
     object_unparent(OBJECT(serverCreds));
     object_unparent(OBJECT(clientCreds));
+    object_unparent(OBJECT(auth));
 
     qcrypto_tls_session_free(serverSess);
     qcrypto_tls_session_free(clientSess);
diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
index 4900c6d433..43b707eba7 100644
--- a/tests/test-io-channel-tls.c
+++ b/tests/test-io-channel-tls.c
@@ -29,8 +29,8 @@
 #include "io-channel-helpers.h"
 #include "crypto/init.h"
 #include "crypto/tlscredsx509.h"
-#include "qemu/acl.h"
 #include "qapi/error.h"
+#include "authz/list.h"
 #include "qom/object_interfaces.h"
 
 #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
@@ -113,7 +113,7 @@ static void test_io_channel_tls(const void *opaque)
     QIOChannelTLS *serverChanTLS;
     QIOChannelSocket *clientChanSock;
     QIOChannelSocket *serverChanSock;
-    qemu_acl *acl;
+    QAuthZList *auth;
     const char * const *wildcards;
     int channel[2];
     struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
@@ -161,11 +161,15 @@ static void test_io_channel_tls(const void *opaque)
         SERVER_CERT_DIR);
     g_assert(serverCreds != NULL);
 
-    acl = qemu_acl_init("channeltlsacl");
-    qemu_acl_reset(acl);
+    auth = qauthz_list_new("channeltlsacl",
+                           QAUTHZ_LIST_POLICY_DENY,
+                           &error_abort);
     wildcards = data->wildcards;
     while (wildcards && *wildcards) {
-        qemu_acl_append(acl, 0, *wildcards);
+        qauthz_list_append_rule(auth, *wildcards,
+                                QAUTHZ_LIST_POLICY_ALLOW,
+                                QAUTHZ_LIST_FORMAT_GLOB,
+                                &error_abort);
         wildcards++;
     }
 
@@ -253,6 +257,8 @@ static void test_io_channel_tls(const void *opaque)
     object_unref(OBJECT(serverChanSock));
     object_unref(OBJECT(clientChanSock));
 
+    object_unparent(OBJECT(auth));
+
     close(channel[0]);
     close(channel[1]);
 }
diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
index 3751a777a4..7b2b09f242 100644
--- a/ui/vnc-auth-sasl.c
+++ b/ui/vnc-auth-sasl.c
@@ -24,6 +24,7 @@
 
 #include "qemu/osdep.h"
 #include "qapi/error.h"
+#include "authz/base.h"
 #include "vnc.h"
 #include "trace.h"
 
@@ -146,13 +147,14 @@ size_t vnc_client_read_sasl(VncState *vs)
 static int vnc_auth_sasl_check_access(VncState *vs)
 {
     const void *val;
-    int err;
-    int allow;
+    int rv;
+    Error *err = NULL;
+    bool allow;
 
-    err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
-    if (err != SASL_OK) {
+    rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
+    if (rv != SASL_OK) {
         trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
-                            sasl_errstring(err, NULL, NULL));
+                            sasl_errstring(rv, NULL, NULL));
         return -1;
     }
     if (val == NULL) {
@@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
     vs->sasl.username = g_strdup((const char*)val);
     trace_vnc_auth_sasl_username(vs, vs->sasl.username);
 
-    if (vs->vd->sasl.acl == NULL) {
+    if (vs->vd->sasl.authzid == NULL) {
         trace_vnc_auth_sasl_acl(vs, 1);
         return 0;
     }
 
-    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
+    allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
+                                    vs->sasl.username, &err);
+    if (err) {
+        trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
+                            error_get_pretty(err));
+        error_free(err);
+        return -1;
+    }
 
     trace_vnc_auth_sasl_acl(vs, allow);
     return allow ? 0 : -1;
diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
index d99ea362c1..f072e16ace 100644
--- a/ui/vnc-auth-vencrypt.c
+++ b/ui/vnc-auth-vencrypt.c
@@ -109,7 +109,7 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
         tls = qio_channel_tls_new_server(
             vs->ioc,
             vs->vd->tlscreds,
-            vs->vd->tlsaclname,
+            vs->vd->tlsauthzid,
             &err);
         if (!tls) {
             trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
index 950f1cd2ac..95c9703c72 100644
--- a/ui/vnc-ws.c
+++ b/ui/vnc-ws.c
@@ -62,7 +62,7 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
     tls = qio_channel_tls_new_server(
         vs->ioc,
         vs->vd->tlscreds,
-        vs->vd->tlsaclname,
+        vs->vd->tlsauthzid,
         &err);
     if (!tls) {
         VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
diff --git a/ui/vnc.c b/ui/vnc.c
index cf221c83cc..60cb7c2d3d 100644
--- a/ui/vnc.c
+++ b/ui/vnc.c
@@ -33,7 +33,7 @@
 #include "qemu/option.h"
 #include "qemu/sockets.h"
 #include "qemu/timer.h"
-#include "qemu/acl.h"
+#include "authz/list.h"
 #include "qemu/config-file.h"
 #include "qapi/qapi-events.h"
 #include "qapi/error.h"
@@ -3267,12 +3267,24 @@ static void vnc_display_close(VncDisplay *vd)
         object_unparent(OBJECT(vd->tlscreds));
         vd->tlscreds = NULL;
     }
-    g_free(vd->tlsaclname);
-    vd->tlsaclname = NULL;
+    if (vd->tlsauthz) {
+        object_unparent(OBJECT(vd->tlsauthz));
+        vd->tlsauthz = NULL;
+    }
+    g_free(vd->tlsauthzid);
+    vd->tlsauthzid = NULL;
     if (vd->lock_key_sync) {
         qemu_remove_led_event_handler(vd->led);
         vd->led = NULL;
     }
+#ifdef CONFIG_VNC_SASL
+    if (vd->sasl.authz) {
+        object_unparent(OBJECT(vd->sasl.authz));
+        vd->sasl.authz = NULL;
+    }
+    g_free(vd->sasl.authzid);
+    vd->sasl.authzid = NULL;
+#endif
 }
 
 int vnc_display_password(const char *id, const char *password)
@@ -3925,23 +3937,24 @@ void vnc_display_open(const char *id, Error **errp)
 
     if (acl) {
         if (strcmp(vd->id, "default") == 0) {
-            vd->tlsaclname = g_strdup("vnc.x509dname");
+            vd->tlsauthzid = g_strdup("vnc.x509dname");
         } else {
-            vd->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vd->id);
+            vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
         }
-        qemu_acl_init(vd->tlsaclname);
+        vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
+                                              QAUTHZ_LIST_POLICY_DENY,
+                                              &error_abort));
     }
 #ifdef CONFIG_VNC_SASL
     if (acl && sasl) {
-        char *aclname;
-
         if (strcmp(vd->id, "default") == 0) {
-            aclname = g_strdup("vnc.username");
+            vd->sasl.authzid = g_strdup("vnc.username");
         } else {
-            aclname = g_strdup_printf("vnc.%s.username", vd->id);
+            vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
         }
-        vd->sasl.acl = qemu_acl_init(aclname);
-        g_free(aclname);
+        vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
+                                                QAUTHZ_LIST_POLICY_DENY,
+                                                &error_abort));
     }
 #endif
 
diff --git a/util/acl.c b/util/acl.c
deleted file mode 100644
index c105addadc..0000000000
--- a/util/acl.c
+++ /dev/null
@@ -1,179 +0,0 @@
-/*
- * QEMU access control list management
- *
- * Copyright (C) 2009 Red Hat, Inc
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to deal
- * in the Software without restriction, including without limitation the rights
- * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
- * copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
- * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
- * THE SOFTWARE.
- */
-
-
-#include "qemu/osdep.h"
-#include "qemu-common.h"
-#include "qemu/acl.h"
-
-#ifdef CONFIG_FNMATCH
-#include <fnmatch.h>
-#endif
-
-
-static unsigned int nacls = 0;
-static qemu_acl **acls = NULL;
-
-
-
-qemu_acl *qemu_acl_find(const char *aclname)
-{
-    int i;
-    for (i = 0 ; i < nacls ; i++) {
-        if (strcmp(acls[i]->aclname, aclname) == 0)
-            return acls[i];
-    }
-
-    return NULL;
-}
-
-qemu_acl *qemu_acl_init(const char *aclname)
-{
-    qemu_acl *acl;
-
-    acl = qemu_acl_find(aclname);
-    if (acl)
-        return acl;
-
-    acl = g_malloc(sizeof(*acl));
-    acl->aclname = g_strdup(aclname);
-    /* Deny by default, so there is no window of "open
-     * access" between QEMU starting, and the user setting
-     * up ACLs in the monitor */
-    acl->defaultDeny = 1;
-
-    acl->nentries = 0;
-    QTAILQ_INIT(&acl->entries);
-
-    acls = g_realloc(acls, sizeof(*acls) * (nacls +1));
-    acls[nacls] = acl;
-    nacls++;
-
-    return acl;
-}
-
-int qemu_acl_party_is_allowed(qemu_acl *acl,
-                              const char *party)
-{
-    qemu_acl_entry *entry;
-
-    QTAILQ_FOREACH(entry, &acl->entries, next) {
-#ifdef CONFIG_FNMATCH
-        if (fnmatch(entry->match, party, 0) == 0)
-            return entry->deny ? 0 : 1;
-#else
-        /* No fnmatch, so fallback to exact string matching
-         * instead of allowing wildcards */
-        if (strcmp(entry->match, party) == 0)
-            return entry->deny ? 0 : 1;
-#endif
-    }
-
-    return acl->defaultDeny ? 0 : 1;
-}
-
-
-void qemu_acl_reset(qemu_acl *acl)
-{
-    qemu_acl_entry *entry, *next_entry;
-
-    /* Put back to deny by default, so there is no window
-     * of "open access" while the user re-initializes the
-     * access control list */
-    acl->defaultDeny = 1;
-    QTAILQ_FOREACH_SAFE(entry, &acl->entries, next, next_entry) {
-        QTAILQ_REMOVE(&acl->entries, entry, next);
-        g_free(entry->match);
-        g_free(entry);
-    }
-    acl->nentries = 0;
-}
-
-
-int qemu_acl_append(qemu_acl *acl,
-                    int deny,
-                    const char *match)
-{
-    qemu_acl_entry *entry;
-
-    entry = g_malloc(sizeof(*entry));
-    entry->match = g_strdup(match);
-    entry->deny = deny;
-
-    QTAILQ_INSERT_TAIL(&acl->entries, entry, next);
-    acl->nentries++;
-
-    return acl->nentries;
-}
-
-
-int qemu_acl_insert(qemu_acl *acl,
-                    int deny,
-                    const char *match,
-                    int index)
-{
-    qemu_acl_entry *tmp;
-    int i = 0;
-
-    if (index <= 0)
-        return -1;
-    if (index > acl->nentries) {
-        return qemu_acl_append(acl, deny, match);
-    }
-
-    QTAILQ_FOREACH(tmp, &acl->entries, next) {
-        i++;
-        if (i == index) {
-            qemu_acl_entry *entry;
-            entry = g_malloc(sizeof(*entry));
-            entry->match = g_strdup(match);
-            entry->deny = deny;
-
-            QTAILQ_INSERT_BEFORE(tmp, entry, next);
-            acl->nentries++;
-            break;
-        }
-    }
-
-    return i;
-}
-
-int qemu_acl_remove(qemu_acl *acl,
-                    const char *match)
-{
-    qemu_acl_entry *entry;
-    int i = 0;
-
-    QTAILQ_FOREACH(entry, &acl->entries, next) {
-        i++;
-        if (strcmp(entry->match, match) == 0) {
-            QTAILQ_REMOVE(&acl->entries, entry, next);
-            acl->nentries--;
-            g_free(entry->match);
-            g_free(entry);
-            return i;
-        }
-    }
-    return -1;
-}
diff --git a/crypto/trace-events b/crypto/trace-events
index 597389b73c..a38ad7b787 100644
--- a/crypto/trace-events
+++ b/crypto/trace-events
@@ -19,5 +19,5 @@ qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "T
 qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
 
 # crypto/tlssession.c
-qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"
+qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *authzid, int endpoint) "TLS session new session=%p creds=%p hostname=%s authzid=%s endpoint=%d"
 qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
diff --git a/tests/Makefile.include b/tests/Makefile.include
index b2369c14cb..e8ceb4e5f6 100644
--- a/tests/Makefile.include
+++ b/tests/Makefile.include
@@ -510,8 +510,8 @@ test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
 test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
 	tests/test-qapi-events.o tests/test-qapi-introspect.o \
 	$(test-qom-obj-y)
-benchmark-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
-test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
+benchmark-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
+test-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
 test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
 test-block-obj-y = $(block-obj-y) $(test-io-obj-y) tests/iothread.o
 
diff --git a/util/Makefile.objs b/util/Makefile.objs
index 4d7675d6e7..ca99e7e90d 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -20,7 +20,6 @@ util-obj-y += envlist.o path.o module.o
 util-obj-y += host-utils.o
 util-obj-y += bitmap.o bitops.o hbitmap.o
 util-obj-y += fifo8.o
-util-obj-y += acl.o
 util-obj-y += cacheinfo.o
 util-obj-y += error.o qemu-error.o
 util-obj-y += id.o
-- 
2.17.2

^ permalink raw reply related	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks Daniel P. Berrangé
@ 2018-10-22 23:54   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
  1 sibling, 0 replies; 36+ messages in thread
From: Philippe Mathieu-Daudé @ 2018-10-22 23:54 UTC (permalink / raw)
  To: Daniel P. Berrangé, qemu-devel
  Cc: Gerd Hoffmann, Dr. David Alan Gilbert, Eric Blake, Markus Armbruster

On 19/10/18 15:38, Daniel P. Berrangé wrote:
> In many cases a single VM will just need to whilelist a single identity
> as the allowed user of network services. This is especially the case for
> TLS live migration (optionally with NBD storage) where we just need to
> whitelist the x509 certificate distinguished name of the source QEMU
> host.
> 
> Via QMP this can be configured with:
> 
>    {
>      "execute": "object-add",
>      "arguments": {
>        "qom-type": "authz-simple",
>        "id": "authz0",
>        "parameters": {
>          "identity": "fred"
>        }
>      }
>    }
> 
> Or via the command line
> 
>    -object authz-simple,id=authz0,identity=fred
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>

> ---
>   include/authz/simple.h |  84 ++++++++++++++++++++++++++++++
>   authz/simple.c         | 115 +++++++++++++++++++++++++++++++++++++++++
>   authz/Makefile.objs    |   1 +
>   authz/trace-events     |   3 ++
>   qemu-options.hx        |  24 +++++++++
>   5 files changed, 227 insertions(+)
>   create mode 100644 include/authz/simple.h
>   create mode 100644 authz/simple.c
> 
> diff --git a/include/authz/simple.h b/include/authz/simple.h
> new file mode 100644
> index 0000000000..4686e7676d
> --- /dev/null
> +++ b/include/authz/simple.h
> @@ -0,0 +1,84 @@
> +/*
> + * QEMU simple authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_SIMPLE_H__
> +#define QAUTHZ_SIMPLE_H__
> +
> +#include "authz/base.h"
> +
> +#define TYPE_QAUTHZ_SIMPLE "authz-simple"
> +
> +#define QAUTHZ_SIMPLE_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZSimpleClass, (klass),        \
> +                       TYPE_QAUTHZ_SIMPLE)
> +#define QAUTHZ_SIMPLE_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZSimpleClass, (obj),    \
> +                      TYPE_QAUTHZ_SIMPLE)
> +#define QAUTHZ_SIMPLE(obj) \
> +    INTERFACE_CHECK(QAuthZSimple, (obj),          \
> +                    TYPE_QAUTHZ_SIMPLE)
> +
> +typedef struct QAuthZSimple QAuthZSimple;
> +typedef struct QAuthZSimpleClass QAuthZSimpleClass;
> +
> +
> +/**
> + * QAuthZSimple:
> + *
> + * This authorization driver provides a simple mechanism
> + * for granting access based on an exact matched username.
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-simple",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "identity": "fred"
> + *      }
> + *    }
> + *  }
> + *
> + * Or via the command line
> + *
> + *   -object authz-simple,id=authz0,identity=fred
> + *
> + */
> +struct QAuthZSimple {
> +    QAuthZ parent_obj;
> +
> +    char *identity;
> +};
> +
> +
> +struct QAuthZSimpleClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZSimple *qauthz_simple_new(const char *id,
> +                                const char *identity,
> +                                Error **errp);
> +
> +
> +#endif /* QAUTHZ_SIMPLE_H__ */
> +
> diff --git a/authz/simple.c b/authz/simple.c
> new file mode 100644
> index 0000000000..8ab718803e
> --- /dev/null
> +++ b/authz/simple.c
> @@ -0,0 +1,115 @@
> +/*
> + * QEMU simple authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/simple.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +
> +static bool qauthz_simple_is_allowed(QAuthZ *authz,
> +                                     const char *identity,
> +                                     Error **errp)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(authz);
> +
> +    trace_qauthz_simple_is_allowed(authz, sauthz->identity, identity);
> +    return g_str_equal(identity, sauthz->identity);
> +}
> +
> +static void
> +qauthz_simple_prop_set_identity(Object *obj,
> +                                const char *value,
> +                                Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    g_free(sauthz->identity);
> +    sauthz->identity = g_strdup(value);
> +}
> +
> +
> +static char *
> +qauthz_simple_prop_get_identity(Object *obj,
> +                                Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    return g_strdup(sauthz->identity);
> +}
> +
> +
> +static void
> +qauthz_simple_finalize(Object *obj)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    g_free(sauthz->identity);
> +}
> +
> +
> +static void
> +qauthz_simple_class_init(ObjectClass *oc, void *data)
> +{
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    authz->is_allowed = qauthz_simple_is_allowed;
> +
> +    object_class_property_add_str(oc, "identity",
> +                                  qauthz_simple_prop_get_identity,
> +                                  qauthz_simple_prop_set_identity,
> +                                  NULL);
> +}
> +
> +
> +QAuthZSimple *qauthz_simple_new(const char *id,
> +                                const char *identity,
> +                                Error **errp)
> +{
> +    return QAUTHZ_SIMPLE(
> +        object_new_with_props(TYPE_QAUTHZ_SIMPLE,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "identity", identity,
> +                              NULL));
> +}
> +
> +
> +static const TypeInfo qauthz_simple_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_SIMPLE,
> +    .instance_size = sizeof(QAuthZSimple),
> +    .instance_finalize = qauthz_simple_finalize,
> +    .class_size = sizeof(QAuthZSimpleClass),
> +    .class_init = qauthz_simple_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_simple_register_types(void)
> +{
> +    type_register_static(&qauthz_simple_info);
> +}
> +
> +
> +type_init(qauthz_simple_register_types);
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 12597c9528..2a75d53840 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1 +1,2 @@
>   authz-obj-y += base.o
> +authz-obj-y += simple.o
> diff --git a/authz/trace-events b/authz/trace-events
> index 481c90f511..1ef796c1e1 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -2,3 +2,6 @@
>   
>   # authz/base.c
>   qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
> +
> +# auth/simple.c
> +qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
> diff --git a/qemu-options.hx b/qemu-options.hx
> index f139459e80..68eaf39cc4 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4377,6 +4377,30 @@ e.g to launch a SEV guest
>        .....
>   
>   @end example
> +
> +
> +@item -object authz-simple,id=@var{id},identity=@var{string}
> +
> +Create an authorization object that will control access to network services.
> +
> +The @option{identity} parameter is identifies the user and its format
> +depends on the network service that authorization object is associated
> +with. For authorizing based on TLS x509 certificates, the identity must
> +be the x509 distinguished name. Note that care must be taken to escape
> +any commas in the distinguished name.
> +
> +An example authorization object to validate a x509 distinguished name
> +would look like:
> +@example
> + # $QEMU \
> +     ...
> +     -object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,O=Example Org,,L=London,,ST=London,,C=GB' \
> +     ...
> +@end example
> +
> +Note the use of quotes due to the x509 distinguished name containing
> +whitespace, and escaping of ','.
> +
>   @end table
>   
>   ETEXI
> 

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file access control list
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file " Daniel P. Berrangé
@ 2018-10-22 23:56   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
  1 sibling, 0 replies; 36+ messages in thread
From: Philippe Mathieu-Daudé @ 2018-10-22 23:56 UTC (permalink / raw)
  To: Daniel P. Berrangé, qemu-devel
  Cc: Gerd Hoffmann, Dr. David Alan Gilbert, Eric Blake, Markus Armbruster

On 19/10/18 15:38, Daniel P. Berrangé wrote:
> Add a QAuthZListFile object type that implements the QAuthZ interface. This
> built-in implementation is a proxy around the QAtuhZList object type,
> initializing it from an external file, and optionally, automatically
> reloading it whenever it changes.
> 
> To create an instance of this object via the QMP monitor, the syntax
> used would be:
> 
>        {
>          "execute": "object-add",
>          "arguments": {
>            "qom-type": "authz-list-file",
>            "id": "authz0",
>            "parameters": {
>              "filename": "/etc/qemu/vnc.acl",
> 	    "refresh": "yes"
>            }
>          }
>        }
> 
> If "refresh" is "yes", inotify is used to monitor the file,
> automatically reloading changes. If an error occurs during reloading,
> all authorizations will fail until the file is next successfully
> loaded.
> 
> The /etc/qemu/vnc.acl file would contain a JSON representation of a
> QAuthZList object
> 
>      {
>        "rules": [
>           { "match": "fred", "policy": "allow", "format": "exact" },
>           { "match": "bob", "policy": "allow", "format": "exact" },
>           { "match": "danb", "policy": "deny", "format": "glob" },
>           { "match": "dan*", "policy": "allow", "format": "exact" },
>        ],
>        "policy": "deny"
>      }
> 
> This sets up an authorization rule that allows 'fred', 'bob' and anyone
> whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> denied.
> 
> The object can be loaded on the comand line using
> 
>     -object authz-list-file,id=authz0,filename=/etc/qemu/vnc.acl,refresh=yes
> 
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>   include/authz/listfile.h | 110 +++++++++++++++
>   authz/listfile.c         | 286 +++++++++++++++++++++++++++++++++++++++
>   authz/Makefile.objs      |   1 +
>   authz/trace-events       |   4 +
>   qemu-options.hx          |  46 +++++++
>   5 files changed, 447 insertions(+)
>   create mode 100644 include/authz/listfile.h
>   create mode 100644 authz/listfile.c
> 
> diff --git a/include/authz/listfile.h b/include/authz/listfile.h
> new file mode 100644
> index 0000000000..244aadc064
> --- /dev/null
> +++ b/include/authz/listfile.h
> @@ -0,0 +1,110 @@
> +/*
> + * QEMU list file authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_LIST_FILE_H__
> +#define QAUTHZ_LIST_FILE_H__
> +
> +#include "authz/list.h"
> +#include "qapi/qapi-types-authz.h"
> +#include "qemu/filemonitor.h"
> +
> +#define TYPE_QAUTHZ_LIST_FILE "authz-list-file"
> +
> +#define QAUTHZ_LIST_FILE_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZListFileClass, (klass),        \
> +                       TYPE_QAUTHZ_LIST_FILE)
> +#define QAUTHZ_LIST_FILE_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZListFileClass, (obj),    \
> +                      TYPE_QAUTHZ_LIST_FILE)
> +#define QAUTHZ_LIST_FILE(obj) \
> +    INTERFACE_CHECK(QAuthZListFile, (obj),          \
> +                    TYPE_QAUTHZ_LIST_FILE)
> +
> +typedef struct QAuthZListFile QAuthZListFile;
> +typedef struct QAuthZListFileClass QAuthZListFileClass;
> +
> +
> +/**
> + * QAuthZListFile:
> + *
> + * This authorization driver provides a file mechanism
> + * for granting access by matching user names against a
> + * file of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-list-file",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "filename": "/etc/qemu/myvm-vnc.acl",
> + *        "refresh": "yes"
> + *      }
> + *    }
> + *  }
> + *
> + * If 'refresh' is 'yes', inotify is used to monitor for changes
> + * to the file and auto-reload the rules.
> + *
> + * The myvm-vnc.acl file should contain the parameters for
> + * the QAuthZList object in JSON format:
> + *
> + *      {
> + *        "rules": [
> + *           { "match": "fred", "policy": "allow", "format": "exact" },
> + *           { "match": "bob", "policy": "allow", "format": "exact" },
> + *           { "match": "danb", "policy": "deny", "format": "exact" },
> + *           { "match": "dan*", "policy": "allow", "format": "glob" }
> + *        ],
> + *        "policy": "deny"
> + *      }
> + *
> + * The object can be created on the command line using
> + *
> + *   -object authz-list-file,id=authz0,\
> + *           filename=/etc/qemu/myvm-vnc.acl,refresh=yes
> + *
> + */
> +struct QAuthZListFile {
> +    QAuthZ parent_obj;
> +
> +    QAuthZ *list;
> +    char *filename;
> +    bool refresh;
> +    QFileMonitor *file_monitor;
> +    int file_watch;
> +};
> +
> +
> +struct QAuthZListFileClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZListFile *qauthz_list_file_new(const char *id,
> +                                     const char *filename,
> +                                     Error **errp);
> +
> +
> +#endif /* QAUTHZ_LIST_FILE_H__ */
> +
> diff --git a/authz/listfile.c b/authz/listfile.c
> new file mode 100644
> index 0000000000..784bec2b1d
> --- /dev/null
> +++ b/authz/listfile.c
> @@ -0,0 +1,286 @@
> +/*
> + * QEMU access control list file authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/listfile.h"
> +#include "authz/trace.h"
> +#include "qemu/error-report.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/sockets.h"
> +#include "qemu/filemonitor.h"
> +#include "qom/object_interfaces.h"
> +#include "qapi/qapi-visit-authz.h"
> +#include "qapi/qmp/qjson.h"
> +#include "qapi/qmp/qobject.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/qobject-input-visitor.h"
> +
> +
> +static bool
> +qauthz_list_file_is_allowed(QAuthZ *authz,
> +                            const char *identity,
> +                            Error **errp)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(authz);
> +    if (fauthz->list) {
> +        return qauthz_is_allowed(fauthz->list, identity, errp);

You could invert this if clause ;)

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>

> +    }
> +
> +    return false;
> +}
> +
> +
> +static QAuthZ *
> +qauthz_list_file_load(QAuthZListFile *fauthz, Error **errp)
> +{
> +    GError *err = NULL;
> +    gchar *content = NULL;
> +    gsize len;
> +    QObject *obj = NULL;
> +    QDict *pdict;
> +    Visitor *v = NULL;
> +    QAuthZ *ret = NULL;
> +
> +    trace_qauthz_list_file_load(fauthz, fauthz->filename);
> +    if (!g_file_get_contents(fauthz->filename, &content, &len, &err)) {
> +        error_setg(errp, "Unable to read '%s': %s",
> +                   fauthz->filename, err->message);
> +        goto cleanup;
> +    }
> +
> +    obj = qobject_from_json(content, errp);
> +    if (!obj) {
> +        goto cleanup;
> +    }
> +
> +    pdict = qobject_to(QDict, obj);
> +    if (!pdict) {
> +        error_setg(errp, QERR_INVALID_PARAMETER_TYPE, "obj", "dict");
> +        goto cleanup;
> +    }
> +
> +    v = qobject_input_visitor_new(obj);
> +
> +    ret = (QAuthZ *)user_creatable_add_type(TYPE_QAUTHZ_LIST,
> +                                            NULL, pdict, v, errp);
> +
> + cleanup:
> +    visit_free(v);
> +    qobject_unref(obj);
> +    if (err) {
> +        g_error_free(err);
> +    }
> +    g_free(content);
> +    return ret;
> +}
> +
> +
> +static void
> +qauthz_list_file_event(int wd G_GNUC_UNUSED,
> +                       QFileMonitorEvent ev G_GNUC_UNUSED,
> +                       const char *name G_GNUC_UNUSED,
> +                       void *opaque)
> +{
> +    QAuthZListFile *fauthz = opaque;
> +    Error *err = NULL;
> +
> +    if (ev != QFILE_MONITOR_EVENT_MODIFIED &&
> +        ev != QFILE_MONITOR_EVENT_CREATED) {
> +        return;
> +    }
> +
> +    object_unref(OBJECT(fauthz->list));
> +    fauthz->list = qauthz_list_file_load(fauthz, &err);
> +    trace_qauthz_list_file_refresh(fauthz,
> +                                   fauthz->filename, fauthz->list ? 1 : 0);
> +    if (!fauthz->list) {
> +        error_report_err(err);
> +    }
> +}
> +
> +static void
> +qauthz_list_file_complete(UserCreatable *uc, Error **errp)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(uc);
> +    gchar *dir = NULL, *file = NULL;
> +
> +    fauthz->list = qauthz_list_file_load(fauthz, errp);
> +
> +    if (!fauthz->refresh) {
> +        return;
> +    }
> +
> +    fauthz->file_monitor = qemu_file_monitor_get_instance(errp);
> +    if (!fauthz->file_monitor) {
> +        return;
> +    }
> +
> +    dir = g_path_get_dirname(fauthz->filename);
> +    if (g_str_equal(dir, ".")) {
> +        error_setg(errp, "Filename must be an absolute path");
> +        goto cleanup;
> +    }
> +    file = g_path_get_basename(fauthz->filename);
> +    if (g_str_equal(file, ".")) {
> +        error_setg(errp, "Path has no trailing filename component");
> +        goto cleanup;
> +    }
> +
> +    fauthz->file_watch = qemu_file_monitor_add_watch(
> +        fauthz->file_monitor, dir, file,
> +        qauthz_list_file_event, fauthz, errp);
> +    if (fauthz->file_watch < 0) {
> +        goto cleanup;
> +    }
> +
> + cleanup:
> +    g_free(file);
> +    g_free(dir);
> +}
> +
> +
> +static void
> +qauthz_list_file_prop_set_filename(Object *obj,
> +                                   const char *value,
> +                                   Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    fauthz->filename = g_strdup(value);
> +}
> +
> +
> +static char *
> +qauthz_list_file_prop_get_filename(Object *obj,
> +                                   Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    return g_strdup(fauthz->filename);
> +}
> +
> +
> +static void
> +qauthz_list_file_prop_set_refresh(Object *obj,
> +                                  bool value,
> +                                  Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    fauthz->refresh = value;
> +}
> +
> +
> +static bool
> +qauthz_list_file_prop_get_refresh(Object *obj,
> +                                  Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    return fauthz->refresh;
> +}
> +
> +
> +static void
> +qauthz_list_file_finalize(Object *obj)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    if (fauthz->file_watch != -1 && fauthz->file_monitor) {
> +        gchar *dir = g_path_get_dirname(fauthz->filename);
> +        qemu_file_monitor_remove_watch(fauthz->file_monitor,
> +                                       dir,
> +                                       fauthz->file_watch);
> +        g_free(dir);
> +    }
> +    object_unref(OBJECT(fauthz->list));
> +    g_free(fauthz->filename);
> +}
> +
> +
> +static void
> +qauthz_list_file_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    ucc->complete = qauthz_list_file_complete;
> +
> +    object_class_property_add_str(oc, "filename",
> +                                  qauthz_list_file_prop_get_filename,
> +                                  qauthz_list_file_prop_set_filename,
> +                                  NULL);
> +    object_class_property_add_bool(oc, "refresh",
> +                                   qauthz_list_file_prop_get_refresh,
> +                                   qauthz_list_file_prop_set_refresh,
> +                                   NULL);
> +
> +    authz->is_allowed = qauthz_list_file_is_allowed;
> +}
> +
> +
> +static void
> +qauthz_list_file_init(Object *obj)
> +{
> +    QAuthZListFile *authz = QAUTHZ_LIST_FILE(obj);
> +
> +    authz->file_watch = -1;
> +#ifdef CONFIG_INOTIFY1
> +    authz->refresh = TRUE;
> +#endif
> +}
> +
> +
> +QAuthZListFile *qauthz_list_file_new(const char *id,
> +                                     const char *filename,
> +                                     Error **errp)
> +{
> +    return QAUTHZ_LIST_FILE(
> +        object_new_with_props(TYPE_QAUTHZ_LIST_FILE,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "filename", filename,
> +                              NULL));
> +}
> +
> +
> +static const TypeInfo qauthz_list_file_info = {
> +    .parent = TYPE_QAUTHZ_LIST,
> +    .name = TYPE_QAUTHZ_LIST_FILE,
> +    .instance_init = qauthz_list_file_init,
> +    .instance_size = sizeof(QAuthZListFile),
> +    .instance_finalize = qauthz_list_file_finalize,
> +    .class_size = sizeof(QAuthZListFileClass),
> +    .class_init = qauthz_list_file_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_list_file_register_types(void)
> +{
> +    type_register_static(&qauthz_list_file_info);
> +}
> +
> +
> +type_init(qauthz_list_file_register_types);
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 921fa624d7..8351bf181d 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1,3 +1,4 @@
>   authz-obj-y += base.o
>   authz-obj-y += simple.o
>   authz-obj-y += list.o
> +authz-obj-y += listfile.o
> diff --git a/authz/trace-events b/authz/trace-events
> index a896d876e8..fb65349a90 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -9,3 +9,7 @@ qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *goti
>   # auth/list.c
>   qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
>   qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
> +
> +# auth/listfile.c
> +qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
> +qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 68eaf39cc4..a1c3e0e59c 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4401,6 +4401,52 @@ would look like:
>   Note the use of quotes due to the x509 distinguished name containing
>   whitespace, and escaping of ','.
>   
> +@item -object authz-listfile,id=@var{id},filename=@var{path},refresh=@var{yes|no}
> +
> +Create an authorization object that will control access to network services.
> +
> +The @option{filename} parameter is the fully qualified path to a file
> +containing the access control list rules in JSON format.
> +
> +An example set of rules that match against SASL usernames might look
> +like:
> +
> +@example
> +  @{
> +    "rules": [
> +       @{ "match": "fred", "policy": "allow", "format": "exact" @},
> +       @{ "match": "bob", "policy": "allow", "format": "exact" @},
> +       @{ "match": "danb", "policy": "deny", "format": "glob" @},
> +       @{ "match": "dan*", "policy": "allow", "format": "exact" @},
> +    ],
> +    "policy": "deny"
> +  @}
> +@end example
> +
> +When checking access the object will iterate over all the rules and
> +the first rule to match will have its @option{policy} value returned
> +as the result. If no rules match, then the default @option{policy}
> +value is returned.
> +
> +The rules can either be an exact string match, or they can use the
> +simple UNIX glob pattern matching to allow wildcards to be used.
> +
> +If @option{refresh} is set to true the file will be monitored
> +and automatically reloaded whenever its content changes.
> +
> +As with the @code{authz-simple} object, the format of the identity
> +strings being matched depends on the network service, but is usually
> +a TLS x509 distinguished name, or a SASL username.
> +
> +An example authorization object to validate a SASL username
> +would look like:
> +@example
> + # $QEMU \
> +     ...
> +     -object authz-simple,id=auth0,filename=/etc/qemu/vnc-sasl.acl,refresh=yes
> +     ...
> +@end example
> +
>   @end table
>   
>   ETEXI
> 

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
@ 2018-10-23 10:18   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
  2018-11-08  8:18   ` Marc-André Lureau
  2 siblings, 0 replies; 36+ messages in thread
From: Philippe Mathieu-Daudé @ 2018-10-23 10:18 UTC (permalink / raw)
  To: Daniel P. Berrangé, qemu-devel
  Cc: Gerd Hoffmann, Dr. David Alan Gilbert, Eric Blake, Markus Armbruster

On 19/10/18 15:38, Daniel P. Berrangé wrote:
> From: "Daniel P. Berrange" <berrange@redhat.com>
> 
> Add a QAuthZList object type that implements the QAuthZ interface. This
> built-in implementation maintains a trivial access control list with a
> sequence of match rules and a final default policy. This replicates the
> functionality currently provided by the qemu_acl module.
> 
> To create an instance of this object via the QMP monitor, the syntax
> used would be:
> 
>    {
>      "execute": "object-add",
>      "arguments": {
>        "qom-type": "authz-list",
>        "id": "authz0",
>        "parameters": {
>          "rules": [
>             { "match": "fred", "policy": "allow", "format": "exact" },
>             { "match": "bob", "policy": "allow", "format": "exact" },
>             { "match": "danb", "policy": "deny", "format": "glob" },
>             { "match": "dan*", "policy": "allow", "format": "exact" },
>          ],
>          "policy": "deny"
>        }
>      }
>    }
> 
> This sets up an authorization rule that allows 'fred', 'bob' and anyone
> whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> denied.
> 
> It is not currently possible to create this via -object, since there is
> no syntax supported to specify non-scalar properties for objects. This
> is likely to be addressed by later support for using JSON with -object,
> or an equivalent approach.
> 
> In any case the future "authz-listfile" object can be used from the
> CLI and is likely a better choice, as it allows the ACL to be refreshed
> automatically on change.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>

> ---
>   Makefile                |   7 +-
>   Makefile.objs           |   4 +
>   qapi/authz.json         |  58 ++++++++
>   qapi/qapi-schema.json   |   1 +
>   include/authz/list.h    | 106 ++++++++++++++
>   authz/list.c            | 309 ++++++++++++++++++++++++++++++++++++++++
>   tests/test-authz-list.c | 171 ++++++++++++++++++++++
>   .gitignore              |   4 +
>   MAINTAINERS             |   1 +
>   authz/Makefile.objs     |   1 +
>   authz/trace-events      |   4 +
>   tests/Makefile.include  |   4 +
>   12 files changed, 669 insertions(+), 1 deletion(-)
>   create mode 100644 qapi/authz.json
>   create mode 100644 include/authz/list.h
>   create mode 100644 authz/list.c
>   create mode 100644 tests/test-authz-list.c
> 
> diff --git a/Makefile b/Makefile
> index 4b20ee2b19..da9ea40725 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -600,7 +600,8 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>                  $(SRC_PATH)/qapi/tpm.json \
>                  $(SRC_PATH)/qapi/trace.json \
>                  $(SRC_PATH)/qapi/transaction.json \
> -               $(SRC_PATH)/qapi/ui.json
> +               $(SRC_PATH)/qapi/ui.json \
> +               $(SRC_PATH)/qapi/authz.json
>   
>   qapi/qapi-builtin-types.c qapi/qapi-builtin-types.h \
>   qapi/qapi-types.c qapi/qapi-types.h \
> @@ -621,6 +622,7 @@ qapi/qapi-types-tpm.c qapi/qapi-types-tpm.h \
>   qapi/qapi-types-trace.c qapi/qapi-types-trace.h \
>   qapi/qapi-types-transaction.c qapi/qapi-types-transaction.h \
>   qapi/qapi-types-ui.c qapi/qapi-types-ui.h \
> +qapi/qapi-types-authz.c qapi/qapi-types-authz.h \
>   qapi/qapi-builtin-visit.c qapi/qapi-builtin-visit.h \
>   qapi/qapi-visit.c qapi/qapi-visit.h \
>   qapi/qapi-visit-block-core.c qapi/qapi-visit-block-core.h \
> @@ -640,6 +642,7 @@ qapi/qapi-visit-tpm.c qapi/qapi-visit-tpm.h \
>   qapi/qapi-visit-trace.c qapi/qapi-visit-trace.h \
>   qapi/qapi-visit-transaction.c qapi/qapi-visit-transaction.h \
>   qapi/qapi-visit-ui.c qapi/qapi-visit-ui.h \
> +qapi/qapi-visit-authz.c qapi/qapi-visit-authz.h \
>   qapi/qapi-commands.h qapi/qapi-commands.c \
>   qapi/qapi-commands-block-core.c qapi/qapi-commands-block-core.h \
>   qapi/qapi-commands-block.c qapi/qapi-commands-block.h \
> @@ -658,6 +661,7 @@ qapi/qapi-commands-tpm.c qapi/qapi-commands-tpm.h \
>   qapi/qapi-commands-trace.c qapi/qapi-commands-trace.h \
>   qapi/qapi-commands-transaction.c qapi/qapi-commands-transaction.h \
>   qapi/qapi-commands-ui.c qapi/qapi-commands-ui.h \
> +qapi/qapi-commands-authz.c qapi/qapi-commands-authz.h \
>   qapi/qapi-events.c qapi/qapi-events.h \
>   qapi/qapi-events-block-core.c qapi/qapi-events-block-core.h \
>   qapi/qapi-events-block.c qapi/qapi-events-block.h \
> @@ -676,6 +680,7 @@ qapi/qapi-events-tpm.c qapi/qapi-events-tpm.h \
>   qapi/qapi-events-trace.c qapi/qapi-events-trace.h \
>   qapi/qapi-events-transaction.c qapi/qapi-events-transaction.h \
>   qapi/qapi-events-ui.c qapi/qapi-events-ui.h \
> +qapi/qapi-events-authz.c qapi/qapi-events-authz.h \
>   qapi/qapi-introspect.h qapi/qapi-introspect.c \
>   qapi/qapi-doc.texi: \
>   qapi-gen-timestamp ;
> diff --git a/Makefile.objs b/Makefile.objs
> index ecb1071c4f..825c5863ac 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -21,6 +21,7 @@ util-obj-y += qapi/qapi-types-tpm.o
>   util-obj-y += qapi/qapi-types-trace.o
>   util-obj-y += qapi/qapi-types-transaction.o
>   util-obj-y += qapi/qapi-types-ui.o
> +util-obj-y += qapi/qapi-types-authz.o
>   util-obj-y += qapi/qapi-builtin-visit.o
>   util-obj-y += qapi/qapi-visit.o
>   util-obj-y += qapi/qapi-visit-block-core.o
> @@ -40,6 +41,7 @@ util-obj-y += qapi/qapi-visit-tpm.o
>   util-obj-y += qapi/qapi-visit-trace.o
>   util-obj-y += qapi/qapi-visit-transaction.o
>   util-obj-y += qapi/qapi-visit-ui.o
> +util-obj-y += qapi/qapi-visit-authz.o
>   util-obj-y += qapi/qapi-events.o
>   util-obj-y += qapi/qapi-events-block-core.o
>   util-obj-y += qapi/qapi-events-block.o
> @@ -58,6 +60,7 @@ util-obj-y += qapi/qapi-events-tpm.o
>   util-obj-y += qapi/qapi-events-trace.o
>   util-obj-y += qapi/qapi-events-transaction.o
>   util-obj-y += qapi/qapi-events-ui.o
> +util-obj-y += qapi/qapi-events-authz.o
>   util-obj-y += qapi/qapi-introspect.o
>   
>   chardev-obj-y = chardev/
> @@ -160,6 +163,7 @@ common-obj-y += qapi/qapi-commands-tpm.o
>   common-obj-y += qapi/qapi-commands-trace.o
>   common-obj-y += qapi/qapi-commands-transaction.o
>   common-obj-y += qapi/qapi-commands-ui.o
> +common-obj-y += qapi/qapi-commands-authz.o
>   common-obj-y += qapi/qapi-introspect.o
>   common-obj-y += qmp.o hmp.o
>   endif
> diff --git a/qapi/authz.json b/qapi/authz.json
> new file mode 100644
> index 0000000000..607839c627
> --- /dev/null
> +++ b/qapi/authz.json
> @@ -0,0 +1,58 @@
> +# -*- Mode: Python -*-
> +#
> +# QAPI authz definitions
> +
> +##
> +# @QAuthZListPolicy:
> +#
> +# The authorization policy result
> +#
> +# @deny: deny access
> +# @allow: allow access
> +#
> +# Since: 3.0
> +##
> +{ 'enum': 'QAuthZListPolicy',
> +  'prefix': 'QAUTHZ_LIST_POLICY',
> +  'data': ['deny', 'allow']}
> +
> +##
> +# @QAuthZListFormat:
> +#
> +# The authorization policy result
> +#
> +# @exact: an exact string match
> +# @glob: string with ? and * shell wildcard support
> +#
> +# Since: 3.0
> +##
> +{ 'enum': 'QAuthZListFormat',
> +  'prefix': 'QAUTHZ_LIST_FORMAT',
> +  'data': ['exact', 'glob']}
> +
> +##
> +# @QAuthZListRule:
> +#
> +# A single authorization rule.
> +#
> +# @match: a glob to match against a user identity
> +# @policy: the result to return if @match evaluates to true
> +# @format: (optional) the format of the @match rule (default 'exact')
> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRule',
> +  'data': {'match': 'str',
> +           'policy': 'QAuthZListPolicy',
> +           '*format': 'QAuthZListFormat'}}
> +
> +##
> +# @QAuthZListRuleListHack:
> +#
> +# Not exposed via QMP; hack to generate QAuthZListRuleList
> +# for use internally by the code.
> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRuleListHack',
> +  'data': { 'unused': ['QAuthZListRule'] } }
> diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
> index 65b6dc2f6f..6a5a02a388 100644
> --- a/qapi/qapi-schema.json
> +++ b/qapi/qapi-schema.json
> @@ -89,6 +89,7 @@
>   { 'include': 'rocker.json' }
>   { 'include': 'tpm.json' }
>   { 'include': 'ui.json' }
> +{ 'include': 'authz.json' }
>   { 'include': 'migration.json' }
>   { 'include': 'transaction.json' }
>   { 'include': 'trace.json' }
> diff --git a/include/authz/list.h b/include/authz/list.h
> new file mode 100644
> index 0000000000..eb131ba0f0
> --- /dev/null
> +++ b/include/authz/list.h
> @@ -0,0 +1,106 @@
> +/*
> + * QEMU list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_LIST_H__
> +#define QAUTHZ_LIST_H__
> +
> +#include "authz/base.h"
> +#include "qapi/qapi-types-authz.h"
> +
> +#define TYPE_QAUTHZ_LIST "authz-list"
> +
> +#define QAUTHZ_LIST_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZListClass, (klass),        \
> +                       TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZListClass, (obj),    \
> +                      TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST(obj) \
> +    INTERFACE_CHECK(QAuthZList, (obj),          \
> +                    TYPE_QAUTHZ_LIST)
> +
> +typedef struct QAuthZList QAuthZList;
> +typedef struct QAuthZListClass QAuthZListClass;
> +
> +
> +/**
> + * QAuthZList:
> + *
> + * This authorization driver provides a list mechanism
> + * for granting access by matching user names against a
> + * list of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-list",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "rules": [
> + *           { "match": "fred", "policy": "allow", "format": "exact" },
> + *           { "match": "bob", "policy": "allow", "format": "exact" },
> + *           { "match": "danb", "policy": "deny", "format": "exact" },
> + *           { "match": "dan*", "policy": "allow", "format": "glob" }
> + *        ],
> + *        "policy": "deny"
> + *      }
> + *    }
> + *  }
> + *
> + */
> +struct QAuthZList {
> +    QAuthZ parent_obj;
> +
> +    QAuthZListPolicy policy;
> +    QAuthZListRuleList *rules;
> +};
> +
> +
> +struct QAuthZListClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp);
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth,
> +                                const char *match);
> +
> +
> +#endif /* QAUTHZ_LIST_H__ */
> +
> diff --git a/authz/list.c b/authz/list.c
> new file mode 100644
> index 0000000000..1d9544681c
> --- /dev/null
> +++ b/authz/list.c
> @@ -0,0 +1,309 @@
> +/*
> + * QEMU access control list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/list.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +#include "qapi/qapi-visit-authz.h"
> +
> +#ifdef CONFIG_FNMATCH
> +#include <fnmatch.h>
> +#endif
> +
> +static bool qauthz_list_is_allowed(QAuthZ *authz,
> +                                   const char *identity,
> +                                   Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(authz);
> +    QAuthZListRuleList *rules = lauthz->rules;
> +
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        QAuthZListFormat format = rule->has_format ? rule->format :
> +            QAUTHZ_LIST_FORMAT_EXACT;
> +
> +        trace_qauthz_list_check_rule(authz, rule->match, identity,
> +                                     format, rule->policy);
> +        switch (format) {
> +        case QAUTHZ_LIST_FORMAT_EXACT:
> +            if (strcmp(rule->match, identity) == 0) {
> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#ifdef CONFIG_FNMATCH
> +        case QAUTHZ_LIST_FORMAT_GLOB:
> +            if (fnmatch(rule->match, identity, 0) == 0) {
> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#else
> +            return false;
> +#endif
> +        default:
> +            return false;
> +        }
> +        rules = rules->next;
> +    }
> +
> +    trace_qauthz_list_default_policy(authz, identity, lauthz->policy);
> +    return lauthz->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +}
> +
> +
> +static void
> +qauthz_list_prop_set_policy(Object *obj,
> +                            int value,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    lauthz->policy = value;
> +}
> +
> +
> +static int
> +qauthz_list_prop_get_policy(Object *obj,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    return lauthz->policy;
> +}
> +
> +
> +static void
> +qauthz_list_prop_get_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +}
> +
> +static void
> +qauthz_list_prop_set_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +    QAuthZListRuleList *oldrules;
> +#ifndef CONFIG_FNMATCH
> +    QAuthZListRuleList *rules;
> +#endif
> +
> +    oldrules = lauthz->rules;
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +
> +#ifndef CONFIG_FNMATCH
> +    rules = lauthz->rules;
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        if (rule->has_format &&
> +            rule->format == QAUTHZ_LIST_FORMAT_GLOB) {
> +            error_setg(errp, "Glob format not supported on this platform");
> +            qapi_free_QAuthZListRuleList(lauthz->rules);
> +            lauthz->rules = oldrules;
> +            return;
> +        }
> +    }
> +#endif
> +
> +    qapi_free_QAuthZListRuleList(oldrules);
> +}
> +
> +
> +static void
> +qauthz_list_finalize(Object *obj)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    qapi_free_QAuthZListRuleList(lauthz->rules);
> +}
> +
> +
> +static void
> +qauthz_list_class_init(ObjectClass *oc, void *data)
> +{
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    object_class_property_add_enum(oc, "policy",
> +                                   "QAuthZListPolicy",
> +                                   &QAuthZListPolicy_lookup,
> +                                   qauthz_list_prop_get_policy,
> +                                   qauthz_list_prop_set_policy,
> +                                   NULL);
> +
> +    object_class_property_add(oc, "rules", "QAuthZListRule",
> +                              qauthz_list_prop_get_rules,
> +                              qauthz_list_prop_set_rules,
> +                              NULL, NULL, NULL);
> +
> +    authz->is_allowed = qauthz_list_is_allowed;
> +}
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp)
> +{
> +    return QAUTHZ_LIST(
> +        object_new_with_props(TYPE_QAUTHZ_LIST,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "policy", QAuthZListPolicy_lookup.array[policy],
> +                              NULL));
> +}
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules) {
> +        while (rules->next) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules && index > 0) {
> +        while (rules->next && i < (index - 1)) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        tmp->next = rules->next;
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        tmp->next = auth->rules;
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *prev;
> +    size_t i = 0;
> +
> +    prev = NULL;
> +    rules = auth->rules;
> +    while (rules) {
> +        rule = rules->value;
> +        if (g_str_equal(rule->match, match)) {
> +            if (prev) {
> +                prev->next = rules->next;
> +            } else {
> +                auth->rules = rules->next;
> +            }
> +            rules->next = NULL;
> +            qapi_free_QAuthZListRuleList(rules);
> +            return i;
> +        }
> +        prev = rules;
> +        rules = rules->next;
> +        i++;
> +    }
> +
> +    return -1;
> +}
> +
> +
> +static const TypeInfo qauthz_list_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_LIST,
> +    .instance_size = sizeof(QAuthZList),
> +    .instance_finalize = qauthz_list_finalize,
> +    .class_size = sizeof(QAuthZListClass),
> +    .class_init = qauthz_list_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_list_register_types(void)
> +{
> +    type_register_static(&qauthz_list_info);
> +}
> +
> +
> +type_init(qauthz_list_register_types);
> diff --git a/tests/test-authz-list.c b/tests/test-authz-list.c
> new file mode 100644
> index 0000000000..02ce4c5763
> --- /dev/null
> +++ b/tests/test-authz-list.c
> @@ -0,0 +1,171 @@
> +/*
> + * QEMU list authorization object
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "authz/list.h"
> +
> +static void test_authz_default_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_default_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +
> +static void test_authz_complex(void)
> +{
> +#ifndef CONFIG_FNMATCH
> +    Error *local_err = NULL;
> +#endif
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +#ifdef CONFIG_FNMATCH
> +    qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
> +#else
> +    g_assert(qauthz_list_append_rule(auth, "dan*",
> +                                     QAUTHZ_LIST_POLICY_ALLOW,
> +                                     QAUTHZ_LIST_FORMAT_GLOB,
> +                                     &local_err) < 0);
> +    g_assert(local_err != NULL);
> +    error_free(local_err);
> +#endif
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_add_remove(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 0);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 1);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 2);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 3);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
> +                    ==, 2);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            2,
> +                                            &error_abort),
> +                    ==, 2);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    module_call_init(MODULE_INIT_QOM);
> +
> +    g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
> +    g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
> +    g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
> +    g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
> +    g_test_add_func("/auth/list/complex", test_authz_complex);
> +    g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
> +
> +    return g_test_run();
> +}
> diff --git a/.gitignore b/.gitignore
> index 64efdfd929..9b9fcad829 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -47,6 +47,7 @@
>   /qapi/qapi-commands-trace.[ch]
>   /qapi/qapi-commands-transaction.[ch]
>   /qapi/qapi-commands-ui.[ch]
> +/qapi/qapi-commands-authz.[ch]
>   /qapi/qapi-commands.[ch]
>   /qapi/qapi-events-block-core.[ch]
>   /qapi/qapi-events-block.[ch]
> @@ -65,6 +66,7 @@
>   /qapi/qapi-events-trace.[ch]
>   /qapi/qapi-events-transaction.[ch]
>   /qapi/qapi-events-ui.[ch]
> +/qapi/qapi-events-authz.[ch]
>   /qapi/qapi-events.[ch]
>   /qapi/qapi-introspect.[ch]
>   /qapi/qapi-types-block-core.[ch]
> @@ -84,6 +86,7 @@
>   /qapi/qapi-types-trace.[ch]
>   /qapi/qapi-types-transaction.[ch]
>   /qapi/qapi-types-ui.[ch]
> +/qapi/qapi-types-authz.[ch]
>   /qapi/qapi-types.[ch]
>   /qapi/qapi-visit-block-core.[ch]
>   /qapi/qapi-visit-block.[ch]
> @@ -102,6 +105,7 @@
>   /qapi/qapi-visit-trace.[ch]
>   /qapi/qapi-visit-transaction.[ch]
>   /qapi/qapi-visit-ui.[ch]
> +/qapi/qapi-visit-authz.[ch]
>   /qapi/qapi-visit.[ch]
>   /qapi/qapi-doc.texi
>   /qemu-doc.html
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9624734923..5d33cf4605 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1861,6 +1861,7 @@ User authorization
>   M: Daniel P. Berrange <berrange@redhat.com>
>   S: Maintained
>   F: authz/
> +F: qapi/authz.json
>   F: include/authz/
>   F: tests/test-authz-*
>   
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 2a75d53840..921fa624d7 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1,2 +1,3 @@
>   authz-obj-y += base.o
>   authz-obj-y += simple.o
> +authz-obj-y += list.o
> diff --git a/authz/trace-events b/authz/trace-events
> index 1ef796c1e1..a896d876e8 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -5,3 +5,7 @@ qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p che
>   
>   # auth/simple.c
>   qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
> +
> +# auth/list.c
> +qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
> +qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index 7fe8578972..b2369c14cb 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -126,6 +126,7 @@ check-unit-y += tests/test-bufferiszero$(EXESUF)
>   check-unit-y += tests/test-uuid$(EXESUF)
>   check-unit-y += tests/ptimer-test$(EXESUF)
>   check-unit-y += tests/test-qapi-util$(EXESUF)
> +check-unit-y += tests/test-authz-list$(EXESUF)
>   
>   check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
>   
> @@ -572,6 +573,9 @@ tests/test-timed-average$(EXESUF): tests/test-timed-average.o $(test-util-obj-y)
>   tests/test-base64$(EXESUF): tests/test-base64.o $(test-util-obj-y)
>   tests/ptimer-test$(EXESUF): tests/ptimer-test.o tests/ptimer-test-stubs.o hw/core/ptimer.o
>   
> +tests/test-authz-list$(EXESUF): tests/test-authz-list.o \
> +	$(test-util-obj-y) $(authz-obj-y) $(qom-obj-y)
> +
>   tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
>   
>   tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
> 

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation Daniel P. Berrangé
@ 2018-10-23 11:14   ` Philippe Mathieu-Daudé
  2018-11-08  8:15   ` Marc-André Lureau
  1 sibling, 0 replies; 36+ messages in thread
From: Philippe Mathieu-Daudé @ 2018-10-23 11:14 UTC (permalink / raw)
  To: Daniel P. Berrangé, qemu-devel
  Cc: Gerd Hoffmann, Dr. David Alan Gilbert, Eric Blake, Markus Armbruster

On 19/10/18 15:38, Daniel P. Berrangé wrote:
> From: "Daniel P. Berrange" <berrange@redhat.com>
> 
> The 'qemu_acl' type was a previous non-QOM based attempt to provide an
> authorization facility in QEMU. Because it is non-QOM based it cannot be
> created via the command line and requires special monitor commands to
> manipulate it.
> 
> The new QAuthZ subclasses provide a superset of the functionality in
> qemu_acl, so the latter can now be deleted. The HMP 'acl_*' monitor
> commands are converted to use the new QAuthZSimple data type instead
> in order to provide temporary backwards compatibility.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>

> ---
>   include/qemu/acl.h             |  66 ------------
>   ui/vnc-auth-sasl.h             |   5 +-
>   ui/vnc.h                       |   4 +-
>   crypto/tlssession.c            |  35 +++----
>   monitor.c                      | 185 ++++++++++++++++++++++-----------
>   tests/test-crypto-tlssession.c |  15 ++-
>   tests/test-io-channel-tls.c    |  16 ++-
>   ui/vnc-auth-sasl.c             |  23 ++--
>   ui/vnc-auth-vencrypt.c         |   2 +-
>   ui/vnc-ws.c                    |   2 +-
>   ui/vnc.c                       |  37 ++++---
>   util/acl.c                     | 179 -------------------------------
>   crypto/trace-events            |   2 +-
>   tests/Makefile.include         |   4 +-
>   util/Makefile.objs             |   1 -
>   15 files changed, 215 insertions(+), 361 deletions(-)
>   delete mode 100644 include/qemu/acl.h
>   delete mode 100644 util/acl.c
> 
> diff --git a/include/qemu/acl.h b/include/qemu/acl.h
> deleted file mode 100644
> index 7c44119a47..0000000000
> --- a/include/qemu/acl.h
> +++ /dev/null
> @@ -1,66 +0,0 @@
> -/*
> - * QEMU access control list management
> - *
> - * Copyright (C) 2009 Red Hat, Inc
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -#ifndef QEMU_ACL_H
> -#define QEMU_ACL_H
> -
> -#include "qemu/queue.h"
> -
> -typedef struct qemu_acl_entry qemu_acl_entry;
> -typedef struct qemu_acl qemu_acl;
> -
> -struct qemu_acl_entry {
> -    char *match;
> -    int deny;
> -
> -    QTAILQ_ENTRY(qemu_acl_entry) next;
> -};
> -
> -struct qemu_acl {
> -    char *aclname;
> -    unsigned int nentries;
> -    QTAILQ_HEAD(,qemu_acl_entry) entries;
> -    int defaultDeny;
> -};
> -
> -qemu_acl *qemu_acl_init(const char *aclname);
> -
> -qemu_acl *qemu_acl_find(const char *aclname);
> -
> -int qemu_acl_party_is_allowed(qemu_acl *acl,
> -			      const char *party);
> -
> -void qemu_acl_reset(qemu_acl *acl);
> -
> -int qemu_acl_append(qemu_acl *acl,
> -		    int deny,
> -		    const char *match);
> -int qemu_acl_insert(qemu_acl *acl,
> -		    int deny,
> -		    const char *match,
> -		    int index);
> -int qemu_acl_remove(qemu_acl *acl,
> -		    const char *match);
> -
> -#endif /* QEMU_ACL_H */
> diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h
> index 2ae224ee3a..fb55fe04ca 100644
> --- a/ui/vnc-auth-sasl.h
> +++ b/ui/vnc-auth-sasl.h
> @@ -30,8 +30,8 @@
>   typedef struct VncStateSASL VncStateSASL;
>   typedef struct VncDisplaySASL VncDisplaySASL;
>   
> -#include "qemu/acl.h"
>   #include "qemu/main-loop.h"
> +#include "authz/base.h"
>   
>   struct VncStateSASL {
>       sasl_conn_t *conn;
> @@ -60,7 +60,8 @@ struct VncStateSASL {
>   };
>   
>   struct VncDisplaySASL {
> -    qemu_acl *acl;
> +    QAuthZ *authz;
> +    char *authzid;
>   };
>   
>   void vnc_sasl_client_cleanup(VncState *vs);
> diff --git a/ui/vnc.h b/ui/vnc.h
> index a86e0610e8..29ee1738a5 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -39,6 +39,7 @@
>   #include "io/channel-socket.h"
>   #include "io/channel-tls.h"
>   #include "io/net-listener.h"
> +#include "authz/base.h"
>   #include <zlib.h>
>   
>   #include "keymaps.h"
> @@ -177,7 +178,8 @@ struct VncDisplay
>       bool lossy;
>       bool non_adaptive;
>       QCryptoTLSCreds *tlscreds;
> -    char *tlsaclname;
> +    QAuthZ *tlsauthz;
> +    char *tlsauthzid;
>   #ifdef CONFIG_VNC_SASL
>       VncDisplaySASL sasl;
>   #endif
> diff --git a/crypto/tlssession.c b/crypto/tlssession.c
> index 66a6fbe19c..23842aa95d 100644
> --- a/crypto/tlssession.c
> +++ b/crypto/tlssession.c
> @@ -24,7 +24,7 @@
>   #include "crypto/tlscredspsk.h"
>   #include "crypto/tlscredsx509.h"
>   #include "qapi/error.h"
> -#include "qemu/acl.h"
> +#include "authz/base.h"
>   #include "trace.h"
>   
>   #ifdef CONFIG_GNUTLS
> @@ -37,7 +37,7 @@ struct QCryptoTLSSession {
>       QCryptoTLSCreds *creds;
>       gnutls_session_t handle;
>       char *hostname;
> -    char *aclname;
> +    char *authzid;
>       bool handshakeComplete;
>       QCryptoTLSSessionWriteFunc writeFunc;
>       QCryptoTLSSessionReadFunc readFunc;
> @@ -56,7 +56,7 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
>       gnutls_deinit(session->handle);
>       g_free(session->hostname);
>       g_free(session->peername);
> -    g_free(session->aclname);
> +    g_free(session->authzid);
>       object_unref(OBJECT(session->creds));
>       g_free(session);
>   }
> @@ -101,7 +101,7 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
>   QCryptoTLSSession *
>   qcrypto_tls_session_new(QCryptoTLSCreds *creds,
>                           const char *hostname,
> -                        const char *aclname,
> +                        const char *authzid,
>                           QCryptoTLSCredsEndpoint endpoint,
>                           Error **errp)
>   {
> @@ -111,13 +111,13 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds,
>       session = g_new0(QCryptoTLSSession, 1);
>       trace_qcrypto_tls_session_new(
>           session, creds, hostname ? hostname : "<none>",
> -        aclname ? aclname : "<none>", endpoint);
> +        authzid ? authzid : "<none>", endpoint);
>   
>       if (hostname) {
>           session->hostname = g_strdup(hostname);
>       }
> -    if (aclname) {
> -        session->aclname = g_strdup(aclname);
> +    if (authzid) {
> +        session->authzid = g_strdup(authzid);
>       }
>       session->creds = creds;
>       object_ref(OBJECT(creds));
> @@ -268,6 +268,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
>       unsigned int nCerts, i;
>       time_t now;
>       gnutls_x509_crt_t cert = NULL;
> +    Error *err = NULL;
>   
>       now = time(NULL);
>       if (now == ((time_t)-1)) {
> @@ -355,19 +356,17 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
>                              gnutls_strerror(ret));
>                   goto error;
>               }
> -            if (session->aclname) {
> -                qemu_acl *acl = qemu_acl_find(session->aclname);
> -                int allow;
> -                if (!acl) {
> -                    error_setg(errp, "Cannot find ACL %s",
> -                               session->aclname);
> +            if (session->authzid) {
> +                bool allow;
> +
> +                allow = qauthz_is_allowed_by_id(session->authzid,
> +                                                session->peername, &err);
> +                if (err) {
> +                    error_propagate(errp, err);
>                       goto error;
>                   }
> -
> -                allow = qemu_acl_party_is_allowed(acl, session->peername);
> -
>                   if (!allow) {
> -                    error_setg(errp, "TLS x509 ACL check for %s is denied",
> +                    error_setg(errp, "TLS x509 authz check for %s is denied",
>                                  session->peername);
>                       goto error;
>                   }
> @@ -558,7 +557,7 @@ qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
>   QCryptoTLSSession *
>   qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
>                           const char *hostname G_GNUC_UNUSED,
> -                        const char *aclname G_GNUC_UNUSED,
> +                        const char *authzid G_GNUC_UNUSED,
>                           QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
>                           Error **errp)
>   {
> diff --git a/monitor.c b/monitor.c
> index b9258a7438..2458322eb1 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -51,7 +51,8 @@
>   #include "sysemu/balloon.h"
>   #include "qemu/timer.h"
>   #include "sysemu/hw_accel.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
> +#include "qapi/util.h"
>   #include "sysemu/tpm.h"
>   #include "qapi/qmp/qdict.h"
>   #include "qapi/qmp/qerror.h"
> @@ -2040,93 +2041,154 @@ static void hmp_wavcapture(Monitor *mon, const QDict *qdict)
>       QLIST_INSERT_HEAD (&capture_head, s, entries);
>   }
>   
> -static qemu_acl *find_acl(Monitor *mon, const char *name)
> +static QAuthZList *find_auth(Monitor *mon, const char *name)
>   {
> -    qemu_acl *acl = qemu_acl_find(name);
> +    Object *obj;
> +    Object *container;
>   
> -    if (!acl) {
> +    container = object_get_objects_root();
> +    obj = object_resolve_path_component(container, name);
> +    if (!obj) {
>           monitor_printf(mon, "acl: unknown list '%s'\n", name);
> +        return NULL;
>       }
> -    return acl;
> +
> +    return QAUTHZ_LIST(obj);
>   }
>   
>   static void hmp_acl_show(Monitor *mon, const QDict *qdict)
>   {
>       const char *aclname = qdict_get_str(qdict, "aclname");
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    qemu_acl_entry *entry;
> -    int i = 0;
> -
> -    if (acl) {
> -        monitor_printf(mon, "policy: %s\n",
> -                       acl->defaultDeny ? "deny" : "allow");
> -        QTAILQ_FOREACH(entry, &acl->entries, next) {
> -            i++;
> -            monitor_printf(mon, "%d: %s %s\n", i,
> -                           entry->deny ? "deny" : "allow", entry->match);
> -        }
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    QAuthZListRuleList *rules;
> +    size_t i = 0;
> +
> +    if (!auth) {
> +        return;
> +    }
> +
> +    monitor_printf(mon, "policy: %s\n",
> +                   QAuthZListPolicy_lookup.array[auth->policy]);
> +
> +    rules = auth->rules;
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        i++;
> +        monitor_printf(mon, "%zu: %s %s\n", i,
> +                       QAuthZListPolicy_lookup.array[rule->policy],
> +                       rule->match);
> +        rules = rules->next;
>       }
>   }
>   
>   static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
>   {
>       const char *aclname = qdict_get_str(qdict, "aclname");
> -    qemu_acl *acl = find_acl(mon, aclname);
> +    QAuthZList *auth = find_auth(mon, aclname);
>   
> -    if (acl) {
> -        qemu_acl_reset(acl);
> -        monitor_printf(mon, "acl: removed all rules\n");
> +    if (!auth) {
> +        return;
>       }
> +
> +    auth->policy = QAUTHZ_LIST_POLICY_DENY;
> +    qapi_free_QAuthZListRuleList(auth->rules);
> +    auth->rules = NULL;
> +    monitor_printf(mon, "acl: removed all rules\n");
>   }
>   
>   static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
>   {
>       const char *aclname = qdict_get_str(qdict, "aclname");
>       const char *policy = qdict_get_str(qdict, "policy");
> -    qemu_acl *acl = find_acl(mon, aclname);
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    int val;
> +    Error *err = NULL;
> +
> +    if (!auth) {
> +        return;
> +    }
>   
> -    if (acl) {
> -        if (strcmp(policy, "allow") == 0) {
> -            acl->defaultDeny = 0;
> +    val = qapi_enum_parse(&QAuthZListPolicy_lookup,
> +                          policy,
> +                          QAUTHZ_LIST_POLICY_DENY,
> +                          &err);
> +    if (err) {
> +        error_free(err);
> +        monitor_printf(mon, "acl: unknown policy '%s', "
> +                       "expected 'deny' or 'allow'\n", policy);
> +    } else {
> +        auth->policy = val;
> +        if (auth->policy == QAUTHZ_LIST_POLICY_ALLOW) {
>               monitor_printf(mon, "acl: policy set to 'allow'\n");
> -        } else if (strcmp(policy, "deny") == 0) {
> -            acl->defaultDeny = 1;
> -            monitor_printf(mon, "acl: policy set to 'deny'\n");
>           } else {
> -            monitor_printf(mon, "acl: unknown policy '%s', "
> -                           "expected 'deny' or 'allow'\n", policy);
> +            monitor_printf(mon, "acl: policy set to 'deny'\n");
>           }
>       }
>   }
>   
> +static QAuthZListFormat hmp_acl_get_format(const char *match)
> +{
> +#ifdef CONFIG_FNMATCH
> +    if (strchr(match, '*')) {
> +        return QAUTHZ_LIST_FORMAT_GLOB;
> +    } else {
> +        return QAUTHZ_LIST_FORMAT_EXACT;
> +    }
> +#else
> +    /* Historically we silently degraded to plain strcmp
> +     * when fnmatch() was missing */
> +    return QAUTHZ_LIST_FORMAT_EXACT;
> +#endif
> +}
> +
>   static void hmp_acl_add(Monitor *mon, const QDict *qdict)
>   {
>       const char *aclname = qdict_get_str(qdict, "aclname");
>       const char *match = qdict_get_str(qdict, "match");
> -    const char *policy = qdict_get_str(qdict, "policy");
> +    const char *policystr = qdict_get_str(qdict, "policy");
>       int has_index = qdict_haskey(qdict, "index");
>       int index = qdict_get_try_int(qdict, "index", -1);
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    int deny, ret;
> -
> -    if (acl) {
> -        if (strcmp(policy, "allow") == 0) {
> -            deny = 0;
> -        } else if (strcmp(policy, "deny") == 0) {
> -            deny = 1;
> -        } else {
> -            monitor_printf(mon, "acl: unknown policy '%s', "
> -                           "expected 'deny' or 'allow'\n", policy);
> -            return;
> -        }
> -        if (has_index)
> -            ret = qemu_acl_insert(acl, deny, match, index);
> -        else
> -            ret = qemu_acl_append(acl, deny, match);
> -        if (ret < 0)
> -            monitor_printf(mon, "acl: unable to add acl entry\n");
> -        else
> -            monitor_printf(mon, "acl: added rule at position %d\n", ret);
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    Error *err = NULL;
> +    QAuthZListPolicy policy;
> +    QAuthZListFormat format;
> +    size_t i = 0;
> +
> +    if (!auth) {
> +        return;
> +    }
> +
> +    policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
> +                             policystr,
> +                             QAUTHZ_LIST_POLICY_DENY,
> +                             &err);
> +    if (err) {
> +        error_free(err);
> +        monitor_printf(mon, "acl: unknown policy '%s', "
> +                       "expected 'deny' or 'allow'\n", policystr);
> +        return;
> +    }
> +
> +    format = hmp_acl_get_format(match);
> +
> +    if (has_index && index == 0) {
> +        monitor_printf(mon, "acl: unable to add acl entry\n");
> +        return;
> +    }
> +
> +    if (has_index) {
> +        i = qauthz_list_insert_rule(auth, match, policy,
> +                                    format, index - 1, &err);
> +    } else {
> +        i = qauthz_list_append_rule(auth, match, policy,
> +                                    format, &err);
> +    }
> +    if (err) {
> +        monitor_printf(mon, "acl: unable to add rule: %s",
> +                       error_get_pretty(err));
> +        error_free(err);
> +    } else {
> +        monitor_printf(mon, "acl: added rule at position %zu\n", i + 1);
>       }
>   }
>   
> @@ -2134,15 +2196,18 @@ static void hmp_acl_remove(Monitor *mon, const QDict *qdict)
>   {
>       const char *aclname = qdict_get_str(qdict, "aclname");
>       const char *match = qdict_get_str(qdict, "match");
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    int ret;
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    ssize_t i = 0;
>   
> -    if (acl) {
> -        ret = qemu_acl_remove(acl, match);
> -        if (ret < 0)
> -            monitor_printf(mon, "acl: no matching acl entry\n");
> -        else
> -            monitor_printf(mon, "acl: removed rule at position %d\n", ret);
> +    if (!auth) {
> +        return;
> +    }
> +
> +    i = qauthz_list_delete_rule(auth, match);
> +    if (i >= 0) {
> +        monitor_printf(mon, "acl: removed rule at position %zu\n", i + 1);
> +    } else {
> +        monitor_printf(mon, "acl: no matching acl entry\n");
>       }
>   }
>   
> diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c
> index 6fa9950afb..15212ec276 100644
> --- a/tests/test-crypto-tlssession.c
> +++ b/tests/test-crypto-tlssession.c
> @@ -28,7 +28,7 @@
>   #include "qom/object_interfaces.h"
>   #include "qapi/error.h"
>   #include "qemu/sockets.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
>   
>   #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
>   
> @@ -229,7 +229,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
>       QCryptoTLSCreds *serverCreds;
>       QCryptoTLSSession *clientSess = NULL;
>       QCryptoTLSSession *serverSess = NULL;
> -    qemu_acl *acl;
> +    QAuthZList *auth;
>       const char * const *wildcards;
>       int channel[2];
>       bool clientShake = false;
> @@ -285,11 +285,15 @@ static void test_crypto_tls_session_x509(const void *opaque)
>           SERVER_CERT_DIR);
>       g_assert(serverCreds != NULL);
>   
> -    acl = qemu_acl_init("tlssessionacl");
> -    qemu_acl_reset(acl);
> +    auth = qauthz_list_new("tlssessionacl",
> +                           QAUTHZ_LIST_POLICY_DENY,
> +                           &error_abort);
>       wildcards = data->wildcards;
>       while (wildcards && *wildcards) {
> -        qemu_acl_append(acl, 0, *wildcards);
> +        qauthz_list_append_rule(auth, *wildcards,
> +                                QAUTHZ_LIST_POLICY_ALLOW,
> +                                QAUTHZ_LIST_FORMAT_GLOB,
> +                                &error_abort);
>           wildcards++;
>       }
>   
> @@ -377,6 +381,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
>   
>       object_unparent(OBJECT(serverCreds));
>       object_unparent(OBJECT(clientCreds));
> +    object_unparent(OBJECT(auth));
>   
>       qcrypto_tls_session_free(serverSess);
>       qcrypto_tls_session_free(clientSess);
> diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
> index 4900c6d433..43b707eba7 100644
> --- a/tests/test-io-channel-tls.c
> +++ b/tests/test-io-channel-tls.c
> @@ -29,8 +29,8 @@
>   #include "io-channel-helpers.h"
>   #include "crypto/init.h"
>   #include "crypto/tlscredsx509.h"
> -#include "qemu/acl.h"
>   #include "qapi/error.h"
> +#include "authz/list.h"
>   #include "qom/object_interfaces.h"
>   
>   #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
> @@ -113,7 +113,7 @@ static void test_io_channel_tls(const void *opaque)
>       QIOChannelTLS *serverChanTLS;
>       QIOChannelSocket *clientChanSock;
>       QIOChannelSocket *serverChanSock;
> -    qemu_acl *acl;
> +    QAuthZList *auth;
>       const char * const *wildcards;
>       int channel[2];
>       struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
> @@ -161,11 +161,15 @@ static void test_io_channel_tls(const void *opaque)
>           SERVER_CERT_DIR);
>       g_assert(serverCreds != NULL);
>   
> -    acl = qemu_acl_init("channeltlsacl");
> -    qemu_acl_reset(acl);
> +    auth = qauthz_list_new("channeltlsacl",
> +                           QAUTHZ_LIST_POLICY_DENY,
> +                           &error_abort);
>       wildcards = data->wildcards;
>       while (wildcards && *wildcards) {
> -        qemu_acl_append(acl, 0, *wildcards);
> +        qauthz_list_append_rule(auth, *wildcards,
> +                                QAUTHZ_LIST_POLICY_ALLOW,
> +                                QAUTHZ_LIST_FORMAT_GLOB,
> +                                &error_abort);
>           wildcards++;
>       }
>   
> @@ -253,6 +257,8 @@ static void test_io_channel_tls(const void *opaque)
>       object_unref(OBJECT(serverChanSock));
>       object_unref(OBJECT(clientChanSock));
>   
> +    object_unparent(OBJECT(auth));
> +
>       close(channel[0]);
>       close(channel[1]);
>   }
> diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
> index 3751a777a4..7b2b09f242 100644
> --- a/ui/vnc-auth-sasl.c
> +++ b/ui/vnc-auth-sasl.c
> @@ -24,6 +24,7 @@
>   
>   #include "qemu/osdep.h"
>   #include "qapi/error.h"
> +#include "authz/base.h"
>   #include "vnc.h"
>   #include "trace.h"
>   
> @@ -146,13 +147,14 @@ size_t vnc_client_read_sasl(VncState *vs)
>   static int vnc_auth_sasl_check_access(VncState *vs)
>   {
>       const void *val;
> -    int err;
> -    int allow;
> +    int rv;
> +    Error *err = NULL;
> +    bool allow;
>   
> -    err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
> -    if (err != SASL_OK) {
> +    rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
> +    if (rv != SASL_OK) {
>           trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
> -                            sasl_errstring(err, NULL, NULL));
> +                            sasl_errstring(rv, NULL, NULL));
>           return -1;
>       }
>       if (val == NULL) {
> @@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
>       vs->sasl.username = g_strdup((const char*)val);
>       trace_vnc_auth_sasl_username(vs, vs->sasl.username);
>   
> -    if (vs->vd->sasl.acl == NULL) {
> +    if (vs->vd->sasl.authzid == NULL) {
>           trace_vnc_auth_sasl_acl(vs, 1);
>           return 0;
>       }
>   
> -    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
> +    allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
> +                                    vs->sasl.username, &err);
> +    if (err) {
> +        trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
> +                            error_get_pretty(err));
> +        error_free(err);
> +        return -1;
> +    }
>   
>       trace_vnc_auth_sasl_acl(vs, allow);
>       return allow ? 0 : -1;
> diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
> index d99ea362c1..f072e16ace 100644
> --- a/ui/vnc-auth-vencrypt.c
> +++ b/ui/vnc-auth-vencrypt.c
> @@ -109,7 +109,7 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
>           tls = qio_channel_tls_new_server(
>               vs->ioc,
>               vs->vd->tlscreds,
> -            vs->vd->tlsaclname,
> +            vs->vd->tlsauthzid,
>               &err);
>           if (!tls) {
>               trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
> diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
> index 950f1cd2ac..95c9703c72 100644
> --- a/ui/vnc-ws.c
> +++ b/ui/vnc-ws.c
> @@ -62,7 +62,7 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
>       tls = qio_channel_tls_new_server(
>           vs->ioc,
>           vs->vd->tlscreds,
> -        vs->vd->tlsaclname,
> +        vs->vd->tlsauthzid,
>           &err);
>       if (!tls) {
>           VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
> diff --git a/ui/vnc.c b/ui/vnc.c
> index cf221c83cc..60cb7c2d3d 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -33,7 +33,7 @@
>   #include "qemu/option.h"
>   #include "qemu/sockets.h"
>   #include "qemu/timer.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
>   #include "qemu/config-file.h"
>   #include "qapi/qapi-events.h"
>   #include "qapi/error.h"
> @@ -3267,12 +3267,24 @@ static void vnc_display_close(VncDisplay *vd)
>           object_unparent(OBJECT(vd->tlscreds));
>           vd->tlscreds = NULL;
>       }
> -    g_free(vd->tlsaclname);
> -    vd->tlsaclname = NULL;
> +    if (vd->tlsauthz) {
> +        object_unparent(OBJECT(vd->tlsauthz));
> +        vd->tlsauthz = NULL;
> +    }
> +    g_free(vd->tlsauthzid);
> +    vd->tlsauthzid = NULL;
>       if (vd->lock_key_sync) {
>           qemu_remove_led_event_handler(vd->led);
>           vd->led = NULL;
>       }
> +#ifdef CONFIG_VNC_SASL
> +    if (vd->sasl.authz) {
> +        object_unparent(OBJECT(vd->sasl.authz));
> +        vd->sasl.authz = NULL;
> +    }
> +    g_free(vd->sasl.authzid);
> +    vd->sasl.authzid = NULL;
> +#endif
>   }
>   
>   int vnc_display_password(const char *id, const char *password)
> @@ -3925,23 +3937,24 @@ void vnc_display_open(const char *id, Error **errp)
>   
>       if (acl) {
>           if (strcmp(vd->id, "default") == 0) {
> -            vd->tlsaclname = g_strdup("vnc.x509dname");
> +            vd->tlsauthzid = g_strdup("vnc.x509dname");
>           } else {
> -            vd->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vd->id);
> +            vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
>           }
> -        qemu_acl_init(vd->tlsaclname);
> +        vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
> +                                              QAUTHZ_LIST_POLICY_DENY,
> +                                              &error_abort));
>       }
>   #ifdef CONFIG_VNC_SASL
>       if (acl && sasl) {
> -        char *aclname;
> -
>           if (strcmp(vd->id, "default") == 0) {
> -            aclname = g_strdup("vnc.username");
> +            vd->sasl.authzid = g_strdup("vnc.username");
>           } else {
> -            aclname = g_strdup_printf("vnc.%s.username", vd->id);
> +            vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
>           }
> -        vd->sasl.acl = qemu_acl_init(aclname);
> -        g_free(aclname);
> +        vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
> +                                                QAUTHZ_LIST_POLICY_DENY,
> +                                                &error_abort));
>       }
>   #endif
>   
> diff --git a/util/acl.c b/util/acl.c
> deleted file mode 100644
> index c105addadc..0000000000
> --- a/util/acl.c
> +++ /dev/null
> @@ -1,179 +0,0 @@
> -/*
> - * QEMU access control list management
> - *
> - * Copyright (C) 2009 Red Hat, Inc
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -
> -#include "qemu/osdep.h"
> -#include "qemu-common.h"
> -#include "qemu/acl.h"
> -
> -#ifdef CONFIG_FNMATCH
> -#include <fnmatch.h>
> -#endif
> -
> -
> -static unsigned int nacls = 0;
> -static qemu_acl **acls = NULL;
> -
> -
> -
> -qemu_acl *qemu_acl_find(const char *aclname)
> -{
> -    int i;
> -    for (i = 0 ; i < nacls ; i++) {
> -        if (strcmp(acls[i]->aclname, aclname) == 0)
> -            return acls[i];
> -    }
> -
> -    return NULL;
> -}
> -
> -qemu_acl *qemu_acl_init(const char *aclname)
> -{
> -    qemu_acl *acl;
> -
> -    acl = qemu_acl_find(aclname);
> -    if (acl)
> -        return acl;
> -
> -    acl = g_malloc(sizeof(*acl));
> -    acl->aclname = g_strdup(aclname);
> -    /* Deny by default, so there is no window of "open
> -     * access" between QEMU starting, and the user setting
> -     * up ACLs in the monitor */
> -    acl->defaultDeny = 1;
> -
> -    acl->nentries = 0;
> -    QTAILQ_INIT(&acl->entries);
> -
> -    acls = g_realloc(acls, sizeof(*acls) * (nacls +1));
> -    acls[nacls] = acl;
> -    nacls++;
> -
> -    return acl;
> -}
> -
> -int qemu_acl_party_is_allowed(qemu_acl *acl,
> -                              const char *party)
> -{
> -    qemu_acl_entry *entry;
> -
> -    QTAILQ_FOREACH(entry, &acl->entries, next) {
> -#ifdef CONFIG_FNMATCH
> -        if (fnmatch(entry->match, party, 0) == 0)
> -            return entry->deny ? 0 : 1;
> -#else
> -        /* No fnmatch, so fallback to exact string matching
> -         * instead of allowing wildcards */
> -        if (strcmp(entry->match, party) == 0)
> -            return entry->deny ? 0 : 1;
> -#endif
> -    }
> -
> -    return acl->defaultDeny ? 0 : 1;
> -}
> -
> -
> -void qemu_acl_reset(qemu_acl *acl)
> -{
> -    qemu_acl_entry *entry, *next_entry;
> -
> -    /* Put back to deny by default, so there is no window
> -     * of "open access" while the user re-initializes the
> -     * access control list */
> -    acl->defaultDeny = 1;
> -    QTAILQ_FOREACH_SAFE(entry, &acl->entries, next, next_entry) {
> -        QTAILQ_REMOVE(&acl->entries, entry, next);
> -        g_free(entry->match);
> -        g_free(entry);
> -    }
> -    acl->nentries = 0;
> -}
> -
> -
> -int qemu_acl_append(qemu_acl *acl,
> -                    int deny,
> -                    const char *match)
> -{
> -    qemu_acl_entry *entry;
> -
> -    entry = g_malloc(sizeof(*entry));
> -    entry->match = g_strdup(match);
> -    entry->deny = deny;
> -
> -    QTAILQ_INSERT_TAIL(&acl->entries, entry, next);
> -    acl->nentries++;
> -
> -    return acl->nentries;
> -}
> -
> -
> -int qemu_acl_insert(qemu_acl *acl,
> -                    int deny,
> -                    const char *match,
> -                    int index)
> -{
> -    qemu_acl_entry *tmp;
> -    int i = 0;
> -
> -    if (index <= 0)
> -        return -1;
> -    if (index > acl->nentries) {
> -        return qemu_acl_append(acl, deny, match);
> -    }
> -
> -    QTAILQ_FOREACH(tmp, &acl->entries, next) {
> -        i++;
> -        if (i == index) {
> -            qemu_acl_entry *entry;
> -            entry = g_malloc(sizeof(*entry));
> -            entry->match = g_strdup(match);
> -            entry->deny = deny;
> -
> -            QTAILQ_INSERT_BEFORE(tmp, entry, next);
> -            acl->nentries++;
> -            break;
> -        }
> -    }
> -
> -    return i;
> -}
> -
> -int qemu_acl_remove(qemu_acl *acl,
> -                    const char *match)
> -{
> -    qemu_acl_entry *entry;
> -    int i = 0;
> -
> -    QTAILQ_FOREACH(entry, &acl->entries, next) {
> -        i++;
> -        if (strcmp(entry->match, match) == 0) {
> -            QTAILQ_REMOVE(&acl->entries, entry, next);
> -            acl->nentries--;
> -            g_free(entry->match);
> -            g_free(entry);
> -            return i;
> -        }
> -    }
> -    return -1;
> -}
> diff --git a/crypto/trace-events b/crypto/trace-events
> index 597389b73c..a38ad7b787 100644
> --- a/crypto/trace-events
> +++ b/crypto/trace-events
> @@ -19,5 +19,5 @@ qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "T
>   qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
>   
>   # crypto/tlssession.c
> -qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"
> +qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *authzid, int endpoint) "TLS session new session=%p creds=%p hostname=%s authzid=%s endpoint=%d"
>   qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index b2369c14cb..e8ceb4e5f6 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -510,8 +510,8 @@ test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
>   test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
>   	tests/test-qapi-events.o tests/test-qapi-introspect.o \
>   	$(test-qom-obj-y)
> -benchmark-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
> -test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
> +benchmark-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
> +test-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
>   test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
>   test-block-obj-y = $(block-obj-y) $(test-io-obj-y) tests/iothread.o
>   
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 4d7675d6e7..ca99e7e90d 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -20,7 +20,6 @@ util-obj-y += envlist.o path.o module.o
>   util-obj-y += host-utils.o
>   util-obj-y += bitmap.o bitops.o hbitmap.o
>   util-obj-y += fifo8.o
> -util-obj-y += acl.o
>   util-obj-y += cacheinfo.o
>   util-obj-y += error.o qemu-error.o
>   util-obj-y += id.o
> 

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner Daniel P. Berrangé
@ 2018-11-07 18:08   ` Marc-André Lureau
  2018-11-12 16:49     ` Daniel P. Berrangé
  0 siblings, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 18:08 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

Hi

On Fri, Oct 19, 2018 at 5:41 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> The inotify userspace API for reading events is quite horrible, so it is
> useful to wrap it in a more friendly API to avoid duplicating code
> across many users in QEMU. Wrapping it also allows introduction of a
> platform portability layer, so that we can add impls for non-Linux based
> equivalents in future.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  include/qemu/filemonitor.h | 117 ++++++++++++++
>  util/filemonitor.c         | 315 +++++++++++++++++++++++++++++++++++++
>  MAINTAINERS                |   6 +
>  util/Makefile.objs         |   1 +
>  util/trace-events          |   9 ++
>  5 files changed, 448 insertions(+)
>  create mode 100644 include/qemu/filemonitor.h
>  create mode 100644 util/filemonitor.c
>
> diff --git a/include/qemu/filemonitor.h b/include/qemu/filemonitor.h
> new file mode 100644
> index 0000000000..1326272f0a
> --- /dev/null
> +++ b/include/qemu/filemonitor.h
> @@ -0,0 +1,117 @@
> +/*
> + * QEMU file monitor helper
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QEMU_FILE_MONITOR_H
> +#define QEMU_FILE_MONITOR_H
> +
> +#include "qemu-common.h"
> +
> +
> +typedef struct QFileMonitor QFileMonitor;
> +
> +typedef enum {
> +    /* File has been created in a dir */
> +    QFILE_MONITOR_EVENT_CREATED,
> +    /* File has been modified in a dir */
> +    QFILE_MONITOR_EVENT_MODIFIED,
> +    /* File has been deleted in a dir */
> +    QFILE_MONITOR_EVENT_DELETED,
> +    /* Dir is no longer being monitored (due to deletion) */
> +    QFILE_MONITOR_EVENT_IGNORED,
> +} QFileMonitorEvent;
> +
> +
> +/**
> + * QFileMonitorHandler:
> + * @id: id from qemu_file_monitor_add_watch()
> + * @event: the file change that occurred
> + * @filename: the name of the file affected
> + * @opaque: opaque data provided to qemu_file_monitor_add_watch()
> + *
> + * Invoked whenever a file changes. If @event is
> + * QFILE_MONITOR_EVENT_IGNORED, @filename will be
> + * empty.
> + *
> + */
> +typedef void (*QFileMonitorHandler)(int id,
> +                                    QFileMonitorEvent event,
> +                                    const char *filename,
> +                                    void *opaque);
> +
> +/**
> + * qemu_file_monitor_get_instance:
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Acquire a handle to the shared file monitoring object.
> + *
> + * This object does locking internally to enable it to be
> + * safe to use from multiple threads
> + *
> + * If the platform does not support file monitoring, an
> + * error will be reported. Likewise if file monitoring
> + * is supported, but cannot be initialized
> + *
> + * Currently this is implemented on Linux platforms with
> + * the inotify subsystem.
> + *
> + * Returns: the shared monitoring object, or NULL on error
> + */
> +QFileMonitor *qemu_file_monitor_get_instance(Error **errp);
> +
> +/**
> + * qemu_file_monitor_add_watch:
> + * @mon: the file monitor context
> + * @dirpath: the directory whose contents to watch
> + * @filename: optional filename to filter on
> + * @cb: the function to invoke when @dirpath has changes
> + * @opaque: data to pass to @cb
> + * @errp: pointer to a NULL-initialized error object
> + *
> + * Register to receive notifications of changes
> + * in the directory @dirpath. All files in the
> + * directory will be monitored. If the caller is
> + * only interested in one specific file, @filename
> + * can be used to filter events.
> + *
> + * Returns: a positive integer watch ID, or -1 on error
> + */
> +int qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                                const char *dirpath,
> +                                const char *filename,
> +                                QFileMonitorHandler cb,
> +                                void *opaque,
> +                                Error **errp);
> +
> +/**
> + * qemu_file_monitor_remove_watch:
> + * @mon: the file monitor context
> + * @dirpath: the directory whose contents to unwatch
> + * @id: id of the watch to remove
> + *
> + * Removes the file monitoring watch @id, associated
> + * with the directory @dirpath. This must never be
> + * called from a QFileMonitorHandler callback, or a
> + * deadlock will result.
> + */
> +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> +                                    const char *dirpath,
> +                                    int id);
> +
> +#endif /* QEMU_FILE_MONITOR_H */
> diff --git a/util/filemonitor.c b/util/filemonitor.c
> new file mode 100644
> index 0000000000..67d7aedbe0
> --- /dev/null
> +++ b/util/filemonitor.c
> @@ -0,0 +1,315 @@
> +/*
> + * QEMU file_monitor helper
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "qemu/filemonitor.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/error-report.h"
> +#include "qapi/error.h"
> +#include "trace.h"
> +
> +struct QFileMonitor {
> +    QemuMutex lock;
> +    int fd;
> +
> +    GHashTable *dirs; /* dirname => QFileMonitorDir */
> +    GHashTable *idmap; /* inotify ID => dirname */
> +};
> +
> +
> +typedef struct {
> +    int id; /* watch ID */
> +    char *filename; /* optional filter */
> +    QFileMonitorHandler cb;
> +    void *opaque;
> +} QFileMonitorWatch;
> +
> +
> +typedef struct {
> +    char *path;
> +    int id; /* inotify ID */
> +    int nextid; /* watch ID counter */
> +    gsize nwatches;
> +    QFileMonitorWatch *watches;
> +} QFileMonitorDir;
> +
> +
> +#ifdef CONFIG_INOTIFY1
> +#include <sys/inotify.h>
> +
> +static void qemu_file_monitor_watch(void *arg)
> +{
> +    QFileMonitor *mon = arg;
> +    char buf[4096]
> +        __attribute__ ((aligned(__alignof__(struct inotify_event))));
> +    int used = 0;
> +    int len = read(mon->fd, buf, sizeof(buf));
> +
> +    qemu_mutex_lock(&mon->lock);

I suppose the lock should guard from mon->fd above, or there might be
a race when modifying/removing a watch from a different thread.

> +
> +    if (len < 0) {
> +        if (errno != EAGAIN) {
> +            error_report("Failure monitoring inotify FD, disabling events");

strerror(errno) could be useful

> +            goto cleanup;
> +        }
> +
> +        /* no more events right now */
> +        goto cleanup;
> +    }
> +
> +    /* Loop over all events in the buffer */
> +    while (used < len) {
> +        struct inotify_event *ev =
> +            (struct inotify_event *)(buf + used);
> +        const char *name = ev->len ? ev->name : "";
> +        QFileMonitorDir *dir = g_hash_table_lookup(mon->idmap,
> +                                                   GINT_TO_POINTER(ev->wd));
> +        uint32_t iev = ev->mask &
> +            (IN_CREATE | IN_MODIFY | IN_DELETE | IN_IGNORED |
> +             IN_MOVED_TO | IN_MOVED_FROM);
> +        int qev;
> +        gsize i;
> +
> +        used += sizeof(struct inotify_event) + ev->len;
> +
> +        if (!dir) {
> +            continue;
> +        }
> +
> +        /*
> +         * During a rename operation, the old name gets
> +         * IN_MOVED_FROM and the new name gets IN_MOVED_TO.
> +         * To simplify life for callers, we turn these into
> +         * DELETED and CREATED events
> +         */
> +        switch (iev) {
> +        case IN_CREATE:
> +        case IN_MOVED_TO:
> +            qev = QFILE_MONITOR_EVENT_CREATED;
> +            break;
> +        case IN_MODIFY:
> +            qev = QFILE_MONITOR_EVENT_MODIFIED;
> +            break;
> +        case IN_DELETE:
> +        case IN_MOVED_FROM:
> +            qev = QFILE_MONITOR_EVENT_DELETED;
> +            break;
> +        case IN_IGNORED:
> +            qev = QFILE_MONITOR_EVENT_IGNORED;
> +            break;
> +        default:
> +            g_assert_not_reached();
> +        }
> +
> +        trace_qemu_file_monitor_event(mon, dir->path, name, ev->mask, dir->id);
> +        for (i = 0; i < dir->nwatches; i++) {
> +            QFileMonitorWatch *watch = &dir->watches[i];
> +
> +            if (watch->filename == NULL ||
> +                (name && g_str_equal(watch->filename, name))) {
> +                trace_qemu_file_monitor_dispatch(mon, dir->path, name,
> +                                                 qev, watch->cb,
> +                                                 watch->opaque, watch->id);
> +                watch->cb(watch->id, qev, name, watch->opaque);
> +            }
> +        }
> +    }
> +
> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +}
> +
> +static void
> +qemu_file_monitor_dir_free(void *data)
> +{
> +    QFileMonitorDir *dir = data;
> +
> +    g_free(dir->watches);

for sake, I would add
assert(dir->nwatches = 0)

> +    g_free(dir);
> +}
> +
> +#endif
> +
> +static QFileMonitor *
> +qemu_file_monitor_new(Error **errp)
> +{
> +#ifdef CONFIG_INOTIFY1
> +    int fd;
> +    QFileMonitor *mon;
> +
> +    fd = inotify_init1(IN_NONBLOCK);
> +    if (fd < 0) {
> +        error_setg_errno(errp, errno,
> +                         "Unable to initialize inotify");
> +        return NULL;
> +    }
> +
> +    mon = g_new0(QFileMonitor, 1);
> +    qemu_mutex_init(&mon->lock);
> +    mon->fd = fd;
> +
> +    mon->dirs = g_hash_table_new_full(g_str_hash, g_str_equal, NULL,
> +                                      qemu_file_monitor_dir_free);
> +    mon->idmap = g_hash_table_new(g_direct_hash, g_direct_equal);
> +
> +    trace_qemu_file_monitor_new(mon, mon->fd);
> +
> +    return mon;
> +#else
> +    error_setg(errp, "File monitoring not available on this platform");
> +    return NULL;
> +#endif
> +}
> +
> +
> +QFileMonitor *qemu_file_monitor_get_instance(Error **errp)
> +{
> +    static QFileMonitor *global;
> +
> +    if (!global) {
> +        global = qemu_file_monitor_new(errp);
> +    }
> +
> +    return global;
> +}
> +
> +
> +#ifdef CONFIG_INOTIFY1
> +int
> +qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                            const char *dirpath,
> +                            const char *filename,
> +                            QFileMonitorHandler cb,
> +                            void *opaque,
> +                            Error **errp)
> +{
> +    QFileMonitorDir *dir;
> +    int ret = -1;
> +
> +    qemu_mutex_lock(&mon->lock);
> +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> +    if (!dir) {
> +        int rv = inotify_add_watch(mon->fd, dirpath,
> +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> +                                   IN_MOVED_TO | IN_MOVED_FROM);
> +
> +        if (rv < 0) {
> +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> +            goto cleanup;
> +        }
> +
> +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> +
> +        dir = g_new0(QFileMonitorDir, 1);
> +        dir->path = g_strdup(dirpath);
> +        dir->id = rv;
> +
> +        g_hash_table_insert(mon->dirs, dir->path, dir);
> +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> +
> +        if (g_hash_table_size(mon->dirs) == 1) {
> +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> +        }
> +    }
> +
> +    dir->watches = g_renew(QFileMonitorWatch, dir->watches, dir->nwatches + 1);

GArray could eventually make handling of watches a bit simpler
(counting, resizing, removing etc)

> +
> +    dir->watches[dir->nwatches].id = ++dir->nextid;
> +    dir->watches[dir->nwatches].filename = filename ? g_strdup(filename) : NULL;

g_strdup(NULL) returns NULL already

> +    dir->watches[dir->nwatches].cb = cb;
> +    dir->watches[dir->nwatches].opaque = opaque;
> +    dir->nwatches++;
> +
> +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> +                                      filename ? filename : "<none>",
> +                                      cb, opaque,
> +                                      dir->watches[dir->nwatches - 1].id);
> +
> +    ret = 0;
> +
> + cleanup:
> +    qemu_mutex_unlock(&mon->lock);
> +    return ret;
> +}
> +
> +
> +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> +                                    const char *dirpath,
> +                                    int id)
> +{
> +    QFileMonitorDir *dir;
> +    gsize i;
> +
> +    qemu_mutex_lock(&mon->lock);
> +
> +    trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
> +
> +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> +    if (!dir) {
> +        goto cleanup;
> +    }
> +
> +    for (i = 0; i < dir->nwatches; i++) {
> +        if (dir->watches[i].id == id) {
> +            if (i < (dir->nwatches - 1)) {
> +                memmove(dir->watches + i,
> +                        dir->watches + i + 1,
> +                        sizeof(QFileMonitorWatch) *
> +                        (dir->nwatches - (i + 1)));
> +                dir->watches = g_renew(QFileMonitorWatch, dir->watches,
> +                                       dir->nwatches - 1);
> +                dir->nwatches--;
> +            }
> +            break;
> +        }
> +    }
> +
> +    if (dir->nwatches == 0) {
> +        inotify_rm_watch(mon->fd, dir->id);
> +        trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
> +
> +        g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
> +        g_hash_table_remove(mon->dirs, dir->path);
> +    }
> +
> + cleanup:
> +    qemu_mutex_lock(&mon->lock);
> +}
> +
> +#else
> +int
> +qemu_file_monitor_add_watch(QFileMonitor *mon,
> +                            const char *dirpath,
> +                            const char *filename,
> +                            QFileMonitorHandler cb,
> +                            void *opaque,
> +                            Error **errp)
> +{
> +    error_setg(errp, "File monitoring not available on this platform");
> +    return -1;
> +}
> +
> +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> +                                    const char *dirpath,
> +                                    int id)
> +{
> +}

Wouldn't it be cleaner with stubs/ ?

> +#endif
> +
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 40672c4eba..29bbcf8c25 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1866,6 +1866,12 @@ F: include/qemu/sockets.h
>  F: util/qemu-sockets.c
>  F: qapi/sockets.json
>
> +File monitor
> +M: Daniel P. Berrange <berrange@redhat.com>
> +S: Odd fixes
> +F: util/filemonitor.c
> +F: include/qemu/filemonitor.h
> +
>  Throttling infrastructure
>  M: Alberto Garcia <berto@igalia.com>
>  S: Supported
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 0820923c18..4d7675d6e7 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -50,5 +50,6 @@ util-obj-y += range.o
>  util-obj-y += stats64.o
>  util-obj-y += systemd.o
>  util-obj-y += iova-tree.o
> +util-obj-y += filemonitor.o
>  util-obj-$(CONFIG_LINUX) += vfio-helpers.o
>  util-obj-$(CONFIG_OPENGL) += drm.o
> diff --git a/util/trace-events b/util/trace-events
> index 79569b7fdf..ff19b253e2 100644
> --- a/util/trace-events
> +++ b/util/trace-events
> @@ -21,6 +21,15 @@ buffer_move_empty(const char *buf, size_t len, const char *from) "%s: %zd bytes
>  buffer_move(const char *buf, size_t len, const char *from) "%s: %zd bytes from %s"
>  buffer_free(const char *buf, size_t len) "%s: capacity %zd"
>
> +# util/filemonitor.c
> +qemu_file_monitor_add_watch(void *mon, const char *dirpath, const char *filename, void *cb, void *opaque, int id) "File monitor %p add watch dir='%s' file='%s' cb=%p opaque=%p id=%u"
> +qemu_file_monitor_remove_watch(void *mon, const char *dirpath, int id) "File monitor %p remove watch dir='%s' id=%u"
> +qemu_file_monitor_new(void *mon, int fd) "File monitor %p created fd=%d"
> +qemu_file_monitor_enable_watch(void *mon, const char *dirpath, int id) "File monitor %p enable watch dir='%s' id=%u"
> +qemu_file_monitor_disable_watch(void *mon, const char *dirpath, int id) "Fle monitor %p disable watch dir='%s' id=%u"
> +qemu_file_monitor_event(void *mon, const char *dirpath, const char *filename, int mask, unsigned int id) "File monitor %p event dir='%s' file='%s' mask=0x%x id=%u"
> +qemu_file_monitor_dispatch(void *mon, const char *dirpath, const char *filename, int ev, void *cb, void *opaque, unsigned int id) "File monitor %p dispatch dir='%s' file='%s' ev=%d cb=%p opaque=%p id=%u"
> +
>  # util/qemu-coroutine.c
>  qemu_aio_coroutine_enter(void *ctx, void *from, void *to, void *opaque) "ctx %p from %p to %p opaque %p"
>  qemu_coroutine_yield(void *from, void *to) "from %p to %p"
> --
> 2.17.2
>
>

Looks good, but would be even better with tests :)

-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered Daniel P. Berrangé
@ 2018-11-07 18:09   ` Marc-André Lureau
  0 siblings, 0 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 18:09 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:39 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> When an object is in turn owned by another user object, it is not
> desirable to expose this in the QOM object hierarchy, as it is
> just an internal implementation detail, we should be free to change
> without exposure.
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  qom/object.c            | 12 ++++++++----
>  qom/object_interfaces.c | 16 ++++++++++------
>  2 files changed, 18 insertions(+), 10 deletions(-)
>
> diff --git a/qom/object.c b/qom/object.c
> index 547dcf97c3..f20f0c45a7 100644
> --- a/qom/object.c
> +++ b/qom/object.c
> @@ -607,15 +607,19 @@ Object *object_new_with_propv(const char *typename,
>          goto error;
>      }
>
> -    object_property_add_child(parent, id, obj, &local_err);
> -    if (local_err) {
> -        goto error;
> +    if (id != NULL) {
> +        object_property_add_child(parent, id, obj, &local_err);
> +        if (local_err) {
> +            goto error;
> +        }
>      }
>
>      if (object_dynamic_cast(obj, TYPE_USER_CREATABLE)) {
>          user_creatable_complete(obj, &local_err);
>          if (local_err) {
> -            object_unparent(obj);
> +            if (id != NULL) {
> +                object_unparent(obj);
> +            }
>              goto error;
>          }
>      }
> diff --git a/qom/object_interfaces.c b/qom/object_interfaces.c
> index 941fd63afd..94d5f91d69 100644
> --- a/qom/object_interfaces.c
> +++ b/qom/object_interfaces.c
> @@ -83,16 +83,20 @@ Object *user_creatable_add_type(const char *type, const char *id,
>          goto out;
>      }
>
> -    object_property_add_child(object_get_objects_root(),
> -                              id, obj, &local_err);
> -    if (local_err) {
> -        goto out;
> +    if (id != NULL) {
> +        object_property_add_child(object_get_objects_root(),
> +                                  id, obj, &local_err);
> +        if (local_err) {
> +            goto out;
> +        }
>      }
>
>      user_creatable_complete(obj, &local_err);
>      if (local_err) {
> -        object_property_del(object_get_objects_root(),
> -                            id, &error_abort);
> +        if (id != NULL) {
> +            object_property_del(object_get_objects_root(),
> +                                id, &error_abort);
> +        }
>          goto out;
>      }
>  out:
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver Daniel P. Berrangé
@ 2018-11-07 18:10   ` Marc-André Lureau
  0 siblings, 0 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 18:10 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:40 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> IN_ISDIR is not a bit that one can request when registering a
> watch with inotify_add_watch. Rather it is a bit that is set
> automatically when reading events from the kernel.
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  hw/usb/dev-mtp.c | 3 +--
>  1 file changed, 1 insertion(+), 2 deletions(-)
>
> diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
> index 00a3691bae..f026419e47 100644
> --- a/hw/usb/dev-mtp.c
> +++ b/hw/usb/dev-mtp.c
> @@ -642,8 +642,7 @@ static void usb_mtp_inotify_cleanup(MTPState *s)
>
>  static int usb_mtp_add_watch(int inotifyfd, char *path)
>  {
> -    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY |
> -        IN_ISDIR;
> +    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
>
>      return inotify_add_watch(inotifyfd, path, mask);
>  }
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params in MTP driver
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params " Daniel P. Berrangé
@ 2018-11-07 18:11   ` Marc-André Lureau
  0 siblings, 0 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 18:11 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:43 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> Various functions accepting 'char *' string parameters were missing
> 'const' qualifiers.
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  hw/usb/dev-mtp.c | 8 ++++----
>  1 file changed, 4 insertions(+), 4 deletions(-)
>
> diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
> index f026419e47..ccbe25820b 100644
> --- a/hw/usb/dev-mtp.c
> +++ b/hw/usb/dev-mtp.c
> @@ -372,7 +372,7 @@ static const USBDesc desc = {
>  /* ----------------------------------------------------------------------- */
>
>  static MTPObject *usb_mtp_object_alloc(MTPState *s, uint32_t handle,
> -                                       MTPObject *parent, char *name)
> +                                       MTPObject *parent, const char *name)
>  {
>      MTPObject *o = g_new0(MTPObject, 1);
>
> @@ -454,7 +454,7 @@ static MTPObject *usb_mtp_object_lookup(MTPState *s, uint32_t handle)
>  }
>
>  static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
> -                                    char *name)
> +                                    const char *name)
>  {
>      MTPObject *child =
>          usb_mtp_object_alloc(s, s->next_handle++, o, name);
> @@ -473,7 +473,7 @@ static MTPObject *usb_mtp_add_child(MTPState *s, MTPObject *o,
>  }
>
>  static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
> -                                             char *name, int len)
> +                                             const char *name, int len)
>  {
>      MTPObject *iter;
>
> @@ -640,7 +640,7 @@ static void usb_mtp_inotify_cleanup(MTPState *s)
>      }
>  }
>
> -static int usb_mtp_add_watch(int inotifyfd, char *path)
> +static int usb_mtp_add_watch(int inotifyfd, const char *path)
>  {
>      uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
>
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs Daniel P. Berrangé
@ 2018-11-07 18:26   ` Marc-André Lureau
  2018-11-13 17:07     ` Daniel P. Berrangé
  0 siblings, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 18:26 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:42 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> The internal inotify APIs allow alot of conditional statements to be

a lot

> cleared out, and provide a simpler callback for handling events.
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  hw/usb/dev-mtp.c    | 250 ++++++++++++++++----------------------------
>  hw/usb/trace-events |   2 +-
>  2 files changed, 93 insertions(+), 159 deletions(-)
>
> diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
> index ccbe25820b..1a8d0f088d 100644
> --- a/hw/usb/dev-mtp.c
> +++ b/hw/usb/dev-mtp.c
> @@ -15,13 +15,11 @@
>  #include <dirent.h>
>
>  #include <sys/statvfs.h>
> -#ifdef CONFIG_INOTIFY1
> -#include <sys/inotify.h>
> -#include "qemu/main-loop.h"
> -#endif
> +
>
>  #include "qemu-common.h"
>  #include "qemu/iov.h"
> +#include "qemu/filemonitor.h"
>  #include "trace.h"
>  #include "hw/usb.h"
>  #include "desc.h"
> @@ -124,7 +122,6 @@ enum {
>      EP_EVENT,
>  };
>
> -#ifdef CONFIG_INOTIFY1
>  typedef struct MTPMonEntry MTPMonEntry;
>
>  struct MTPMonEntry {
> @@ -133,7 +130,6 @@ struct MTPMonEntry {
>
>      QTAILQ_ENTRY(MTPMonEntry) next;
>  };
> -#endif
>
>  struct MTPControl {
>      uint16_t     code;
> @@ -162,10 +158,8 @@ struct MTPObject {
>      char         *name;
>      char         *path;
>      struct stat  stat;
> -#ifdef CONFIG_INOTIFY1
> -    /* inotify watch cookie */
> +    /* file monitor watch cookie */
>      int          watchfd;

Why not rename it watchid to avoid confusion?

> -#endif
>      MTPObject    *parent;
>      uint32_t     nchildren;
>      QLIST_HEAD(, MTPObject) children;
> @@ -188,11 +182,8 @@ struct MTPState {
>      bool         readonly;
>
>      QTAILQ_HEAD(, MTPObject) objects;
> -#ifdef CONFIG_INOTIFY1
> -    /* inotify descriptor */
> -    int          inotifyfd;
> +    QFileMonitor *file_monitor;
>      QTAILQ_HEAD(events, MTPMonEntry) events;
> -#endif
>      /* Responder is expecting a write operation */
>      bool write_pending;
>      struct {
> @@ -477,6 +468,10 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
>  {
>      MTPObject *iter;
>
> +    if (len == -1) {
> +        len = strlen(name);
> +    }
> +
>      QLIST_FOREACH(iter, &parent->children, list) {
>          if (strncmp(iter->name, name, len) == 0) {
>              return iter;
> @@ -486,7 +481,6 @@ static MTPObject *usb_mtp_object_lookup_name(MTPObject *parent,
>      return NULL;
>  }
>
> -#ifdef CONFIG_INOTIFY1
>  static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
>  {
>      MTPObject *iter;
> @@ -500,158 +494,98 @@ static MTPObject *usb_mtp_object_lookup_wd(MTPState *s, int wd)
>      return NULL;
>  }
>
> -static void inotify_watchfn(void *arg)
> +static void file_monitor_event(int wd,
> +                               QFileMonitorEvent ev,
> +                               const char *name,
> +                               void *opaque)
>  {
> -    MTPState *s = arg;
> -    ssize_t bytes;
> -    /* From the man page: atleast one event can be read */
> -    int pos;
> -    char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
> -
> -    for (;;) {
> -        bytes = read(s->inotifyfd, buf, sizeof(buf));
> -        pos = 0;
> -
> -        if (bytes <= 0) {
> -            /* Better luck next time */
> +    MTPState *s = opaque;
> +    int watchfd = 0;
> +    MTPObject *parent = usb_mtp_object_lookup_wd(s, wd);
> +    MTPMonEntry *entry = NULL;
> +    MTPObject *o;
> +
> +    if (!parent) {
> +        return;
> +    }
> +
> +    switch (ev) {
> +    case QFILE_MONITOR_EVENT_CREATED:
> +        if (usb_mtp_object_lookup_name(parent, name, -1)) {
> +            /* Duplicate create event */
>              return;
>          }
> +        entry = g_new0(MTPMonEntry, 1);
> +        entry->handle = s->next_handle;
> +        entry->event = EVT_OBJ_ADDED;
> +        o = usb_mtp_add_child(s, parent, name);
> +        if (!o) {
> +            g_free(entry);
> +            return;
> +        }
> +        o->watchfd = watchfd;

this effectively always set o->watchfd to 0, which is already
initialized to 0 with g_new0(), you can drop it

> +        trace_usb_mtp_file_monitor_event(s->dev.addr, name, "Obj Added");
> +        break;
>
> +    case QFILE_MONITOR_EVENT_DELETED:
>          /*
> -         * TODO: Ignore initiator initiated events.
> -         * For now we are good because the store is RO
> +         * The kernel issues a IN_IGNORED event
> +         * when a dir containing a watchpoint is
> +         * deleted, so we don't have to delete the
> +         * watchpoint
>           */
> -        while (bytes > 0) {
> -            char *p = buf + pos;
> -            struct inotify_event *event = (struct inotify_event *)p;
> -            int watchfd = 0;
> -            uint32_t mask = event->mask & (IN_CREATE | IN_DELETE |
> -                                           IN_MODIFY | IN_IGNORED);
> -            MTPObject *parent = usb_mtp_object_lookup_wd(s, event->wd);
> -            MTPMonEntry *entry = NULL;
> -            MTPObject *o;
> -
> -            pos = pos + sizeof(struct inotify_event) + event->len;
> -            bytes = bytes - pos;
> -
> -            if (!parent) {
> -                continue;
> -            }
> -
> -            switch (mask) {
> -            case IN_CREATE:
> -                if (usb_mtp_object_lookup_name
> -                    (parent, event->name, event->len)) {
> -                    /* Duplicate create event */
> -                    continue;
> -                }
> -                entry = g_new0(MTPMonEntry, 1);
> -                entry->handle = s->next_handle;
> -                entry->event = EVT_OBJ_ADDED;
> -                o = usb_mtp_add_child(s, parent, event->name);
> -                if (!o) {
> -                    g_free(entry);
> -                    continue;
> -                }
> -                o->watchfd = watchfd;
> -                trace_usb_mtp_inotify_event(s->dev.addr, event->name,
> -                                            event->mask, "Obj Added");
> -                break;
> -
> -            case IN_DELETE:
> -                /*
> -                 * The kernel issues a IN_IGNORED event
> -                 * when a dir containing a watchpoint is
> -                 * deleted, so we don't have to delete the
> -                 * watchpoint
> -                 */
> -                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
> -                if (!o) {
> -                    continue;
> -                }
> -                entry = g_new0(MTPMonEntry, 1);
> -                entry->handle = o->handle;
> -                entry->event = EVT_OBJ_REMOVED;
> -                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
> -                                      event->mask, "Obj Deleted");
> -                usb_mtp_object_free(s, o);
> -                break;
> -
> -            case IN_MODIFY:
> -                o = usb_mtp_object_lookup_name(parent, event->name, event->len);
> -                if (!o) {
> -                    continue;
> -                }
> -                entry = g_new0(MTPMonEntry, 1);
> -                entry->handle = o->handle;
> -                entry->event = EVT_OBJ_INFO_CHANGED;
> -                trace_usb_mtp_inotify_event(s->dev.addr, o->path,
> -                                      event->mask, "Obj Modified");
> -                break;
> -
> -            case IN_IGNORED:
> -                trace_usb_mtp_inotify_event(s->dev.addr, parent->path,
> -                                      event->mask, "Obj parent dir ignored");
> -                break;
> -
> -            default:
> -                fprintf(stderr, "usb-mtp: failed to parse inotify event\n");
> -                continue;
> -            }
> +        o = usb_mtp_object_lookup_name(parent, name, -1);
> +        if (!o) {
> +            return;
> +        }
> +        entry = g_new0(MTPMonEntry, 1);
> +        entry->handle = o->handle;
> +        entry->event = EVT_OBJ_REMOVED;
> +        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Deleted");
> +        usb_mtp_object_free(s, o);
> +        break;
>
> -            if (entry) {
> -                QTAILQ_INSERT_HEAD(&s->events, entry, next);
> -            }
> +    case QFILE_MONITOR_EVENT_MODIFIED:
> +        o = usb_mtp_object_lookup_name(parent, name, -1);
> +        if (!o) {
> +            return;
>          }
> -    }
> -}
> +        entry = g_new0(MTPMonEntry, 1);
> +        entry->handle = o->handle;
> +        entry->event = EVT_OBJ_INFO_CHANGED;
> +        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path, "Obj Modified");
> +        break;
>
> -static int usb_mtp_inotify_init(MTPState *s)
> -{
> -    int fd;
> +    case QFILE_MONITOR_EVENT_IGNORED:
> +        trace_usb_mtp_file_monitor_event(s->dev.addr, parent->path,
> +                                    "Obj parent dir ignored");
> +        break;
>
> -    fd = inotify_init1(IN_NONBLOCK);
> -    if (fd == -1) {
> -        return 1;
> +    default:
> +        g_assert_not_reached();
>      }
>
> -    QTAILQ_INIT(&s->events);
> -    s->inotifyfd = fd;
> -
> -    qemu_set_fd_handler(fd, inotify_watchfn, NULL, s);
> -
> -    return 0;
> +    if (entry) {
> +        QTAILQ_INSERT_HEAD(&s->events, entry, next);
> +    }
>  }
>
> -static void usb_mtp_inotify_cleanup(MTPState *s)
> +static void usb_mtp_file_monitor_cleanup(MTPState *s)
>  {
>      MTPMonEntry *e, *p;
>
> -    if (!s->inotifyfd) {
> -        return;
> -    }
> -
> -    qemu_set_fd_handler(s->inotifyfd, NULL, NULL, s);
> -    close(s->inotifyfd);
> -
>      QTAILQ_FOREACH_SAFE(e, &s->events, next, p) {
>          QTAILQ_REMOVE(&s->events, e, next);
>          g_free(e);
>      }
>  }
>
> -static int usb_mtp_add_watch(int inotifyfd, const char *path)
> -{
> -    uint32_t mask = IN_CREATE | IN_DELETE | IN_MODIFY;
> -
> -    return inotify_add_watch(inotifyfd, path, mask);
> -}
> -#endif
>
>  static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
>  {
>      struct dirent *entry;
>      DIR *dir;
> +    Error *err = NULL;
>
>      if (o->have_children) {
>          return;
> @@ -662,16 +596,19 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
>      if (!dir) {
>          return;
>      }
> -#ifdef CONFIG_INOTIFY1
> -    int watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
> +
> +    int watchfd = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
> +                                              file_monitor_event, s, &err);

There is an add_watch(), but I don't see the corresponding
remove_watch(). This may probably cause crashes if MTPState is freed.

>      if (watchfd == -1) {
> -        fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
> +        fprintf(stderr, "usb-mtp: failed to add watch for %s: %s\n", o->path,
> +                error_get_pretty(err));

maybe it's a good time to turn into error_report() ?

> +        error_free(err);
>      } else {
> -        trace_usb_mtp_inotify_event(s->dev.addr, o->path,
> -                                    0, "Watch Added");
> +        trace_usb_mtp_file_monitor_event(s->dev.addr, o->path,
> +                                         "Watch Added");
>          o->watchfd = watchfd;
>      }
> -#endif
> +
>      while ((entry = readdir(dir)) != NULL) {
>          usb_mtp_add_child(s, o, entry->d_name);
>      }
> @@ -1179,13 +1116,11 @@ enum {
>  /* Assumes that children, if any, have been already freed */
>  static void usb_mtp_object_free_one(MTPState *s, MTPObject *o)
>  {
> -#ifndef CONFIG_INOTIFY1
>      assert(o->nchildren == 0);
>      QTAILQ_REMOVE(&s->objects, o, next);
>      g_free(o->name);
>      g_free(o->path);
>      g_free(o);
> -#endif
>  }
>
>  static int usb_mtp_deletefn(MTPState *s, MTPObject *o, uint32_t trans)
> @@ -1284,6 +1219,7 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
>      MTPData *data_in = NULL;
>      MTPObject *o = NULL;
>      uint32_t nres = 0, res0 = 0;
> +    Error *err = NULL;
>
>      /* sanity checks */
>      if (c->code >= CMD_CLOSE_SESSION && s->session == 0) {
> @@ -1311,19 +1247,21 @@ static void usb_mtp_command(MTPState *s, MTPControl *c)
>          trace_usb_mtp_op_open_session(s->dev.addr);
>          s->session = c->argv[0];
>          usb_mtp_object_alloc(s, s->next_handle++, NULL, s->root);
> -#ifdef CONFIG_INOTIFY1
> -        if (usb_mtp_inotify_init(s)) {
> -            fprintf(stderr, "usb-mtp: file monitoring init failed\n");
> +
> +        s->file_monitor = qemu_file_monitor_get_instance(&err);
> +        if (err) {
> +            fprintf(stderr, "usb-mtp: file monitoring init failed: %s\n",
> +                    error_get_pretty(err));
> +            error_free(err);
> +        } else {
> +            QTAILQ_INIT(&s->events);
>          }
> -#endif
>          break;
>      case CMD_CLOSE_SESSION:
>          trace_usb_mtp_op_close_session(s->dev.addr);
>          s->session = 0;
>          s->next_handle = 0;
> -#ifdef CONFIG_INOTIFY1
> -        usb_mtp_inotify_cleanup(s);
> -#endif
> +        usb_mtp_file_monitor_cleanup(s);
>          usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
>          assert(QTAILQ_EMPTY(&s->objects));
>          break;
> @@ -1536,9 +1474,7 @@ static void usb_mtp_handle_reset(USBDevice *dev)
>
>      trace_usb_mtp_reset(s->dev.addr);
>
> -#ifdef CONFIG_INOTIFY1
> -    usb_mtp_inotify_cleanup(s);
> -#endif
> +    usb_mtp_file_monitor_cleanup(s);
>      usb_mtp_object_free(s, QTAILQ_FIRST(&s->objects));
>      s->session = 0;
>      usb_mtp_data_free(s->data_in);
> @@ -1967,7 +1903,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
>          }
>          break;
>      case EP_EVENT:
> -#ifdef CONFIG_INOTIFY1
>          if (!QTAILQ_EMPTY(&s->events)) {
>              struct MTPMonEntry *e = QTAILQ_LAST(&s->events, events);
>              uint32_t handle;
> @@ -1991,7 +1926,6 @@ static void usb_mtp_handle_data(USBDevice *dev, USBPacket *p)
>              g_free(e);
>              return;
>          }
> -#endif
>          p->status = USB_RET_NAK;
>          return;
>      default:
> diff --git a/hw/usb/trace-events b/hw/usb/trace-events
> index 2c18770ca5..99b1e8b8ce 100644
> --- a/hw/usb/trace-events
> +++ b/hw/usb/trace-events
> @@ -237,7 +237,7 @@ usb_mtp_op_unknown(int dev, uint32_t code) "dev %d, command code 0x%x"
>  usb_mtp_object_alloc(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
>  usb_mtp_object_free(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
>  usb_mtp_add_child(int dev, uint32_t handle, const char *path) "dev %d, handle 0x%x, path %s"
> -usb_mtp_inotify_event(int dev, const char *path, uint32_t mask, const char *s) "dev %d, path %s mask 0x%x event %s"
> +usb_mtp_file_monitor_event(int dev, const char *path, const char *s) "dev %d, path %s event %s"
>
>  # hw/usb/host-libusb.c
>  usb_host_open_started(int bus, int addr) "dev %d:%d"
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM Daniel P. Berrangé
@ 2018-11-07 22:23   ` Marc-André Lureau
  2018-11-15 10:32     ` Daniel P. Berrangé
  0 siblings, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 22:23 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

Hi

On Fri, Oct 19, 2018 at 5:47 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> 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.
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  configure               |  37 ++++++++++
>  include/authz/pamacct.h | 100 +++++++++++++++++++++++++++
>  authz/pamacct.c         | 149 ++++++++++++++++++++++++++++++++++++++++
>  authz/Makefile.objs     |   3 +
>  authz/trace-events      |   3 +
>  qemu-options.hx         |  35 ++++++++++
>  6 files changed, 327 insertions(+)
>  create mode 100644 include/authz/pamacct.h
>  create mode 100644 authz/pamacct.c
>
> diff --git a/configure b/configure
> index 9138af37f8..db35d52e12 100755
> --- a/configure
> +++ b/configure
> @@ -463,6 +463,7 @@ nettle_kdf="no"
>  gcrypt=""
>  gcrypt_hmac="no"
>  gcrypt_kdf="no"
> +auth_pam=""
>  vte=""
>  virglrenderer=""
>  tpm="yes"
> @@ -1361,6 +1362,10 @@ for opt do
>    ;;
>    --enable-gcrypt) gcrypt="yes"
>    ;;
> +  --disable-auth-pam) auth_pam="no"
> +  ;;
> +  --enable-auth-pam) auth_pam="yes"
> +  ;;
>    --enable-rdma) rdma="yes"
>    ;;
>    --disable-rdma) rdma="no"
> @@ -1653,6 +1658,7 @@ disabled with --disable-FEATURE, default is enabled if available:
>    gnutls          GNUTLS cryptography support
>    nettle          nettle cryptography support
>    gcrypt          libgcrypt cryptography support
> +  auth-pam        PAM access control
>    sdl             SDL UI
>    --with-sdlabi     select preferred SDL ABI 1.2 or 2.0
>    gtk             gtk UI
> @@ -2876,6 +2882,33 @@ else
>  fi
>
>
> +##########################################
> +# PAM probe
> +
> +if test "x$auth_pam" != "no"; then
> +    cat > $TMPC <<EOF
> +#include <security/pam_appl.h>
> +#include <stdio.h>
> +int main(void) {
> +   const char *service_name = "qemu";
> +   const char *user = "frank";
> +   const struct pam_conv *pam_conv = NULL;
> +   pam_handle_t *pamh = NULL;
> +   pam_start(service_name, user, pam_conv, &pamh);
> +   return 0;
> +}
> +EOF
> +    if compile_prog "" "-lpam" ; then
> +       auth_pam=yes
> +    else
> +       if test "$auth_pam" = "yes"; then
> +           feature_not_found "PAM" "Install PAM development package"
> +       else
> +           auth_pam=no
> +       fi
> +    fi
> +fi
> +
>  ##########################################
>  # getifaddrs (for tests/test-io-channel-socket )
>
> @@ -5967,6 +6000,7 @@ echo "libgcrypt kdf     $gcrypt_kdf"
>  echo "nettle            $nettle $(echo_version $nettle $nettle_version)"
>  echo "nettle kdf        $nettle_kdf"
>  echo "libtasn1          $tasn1"
> +echo "PAM               $auth_pam"
>  echo "curses support    $curses"
>  echo "virgl support     $virglrenderer $(echo_version $virglrenderer $virgl_version)"
>  echo "curl support      $curl"
> @@ -6423,6 +6457,9 @@ fi
>  if test "$tasn1" = "yes" ; then
>    echo "CONFIG_TASN1=y" >> $config_host_mak
>  fi
> +if test "$auth_pam" = "yes" ; then
> +    echo "CONFIG_AUTH_PAM=y" >> $config_host_mak
> +fi
>  if test "$have_ifaddrs_h" = "yes" ; then
>      echo "HAVE_IFADDRS_H=y" >> $config_host_mak
>  fi
> diff --git a/include/authz/pamacct.h b/include/authz/pamacct.h
> new file mode 100644
> index 0000000000..d2c0149153
> --- /dev/null
> +++ b/include/authz/pamacct.h
> @@ -0,0 +1,100 @@
> +/*
> + * QEMU PAM authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_PAM_H__
> +#define QAUTHZ_PAM_H__
> +
> +#include "authz/base.h"
> +
> +
> +#define TYPE_QAUTHZ_PAM "authz-pam"
> +
> +#define QAUTHZ_PAM_CLASS(klass) \
> +     OBJECT_CLASS_CHECK(QAuthZPamClass, (klass), \
> +                        TYPE_QAUTHZ_PAM)
> +#define QAUTHZ_PAM_GET_CLASS(obj) \
> +     OBJECT_GET_CLASS(QAuthZPamClass, (obj), \
> +                      TYPE_QAUTHZ_PAM)
> +#define QAUTHZ_PAM(obj) \
> +     INTERFACE_CHECK(QAuthZPam, (obj), \
> +                     TYPE_QAUTHZ_PAM)
> +
> +typedef struct QAuthZPam QAuthZPam;
> +typedef struct QAuthZPamClass QAuthZPamClass;
> +
> +
> +/**
> + * QAuthZPam:
> + *
> + * This authorization driver provides a PAM mechanism
> + * for granting access by matching user names against a
> + * list of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-pam",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "service": "qemu-vnc-tls"
> + *      }
> + *    }
> + *  }
> + *
> + * The driver only uses the PAM "account" verification
> + * subsystem. The above config would require a config
> + * file /etc/pam.d/qemu-vnc-tls. For a simple file
> + * lookup it would contain
> + *
> + *   account requisite  pam_listfile.so item=user sense=allow \
> + *           file=/etc/qemu/vnc.allow
> + *
> + * The external file would then contain a list of usernames.
> + * If x509 cert was being used as the username, a suitable
> + * entry would match the distinguish name:
> + *
> + *  CN=laptop.berrange.com,O=Berrange Home,L=London,ST=London,C=GB
> + *
> + * On the command line it can be created using
> + *
> + *   -object authz-pam,id=authz0,service=qemu-vnc-tls
> + *
> + */
> +struct QAuthZPam {
> +    QAuthZ parent_obj;
> +
> +    char *service;
> +};
> +
> +
> +struct QAuthZPamClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZPam *qauthz_pam_new(const char *id,
> +                          const char *service,
> +                          Error **errp);
> +
> +
> +#endif /* QAUTHZ_PAM_H__ */
> diff --git a/authz/pamacct.c b/authz/pamacct.c
> new file mode 100644
> index 0000000000..a070dda217
> --- /dev/null
> +++ b/authz/pamacct.c
> @@ -0,0 +1,149 @@
> +/*
> + * QEMU PAM authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/pamacct.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +
> +#include <security/pam_appl.h>
> +
> +
> +static bool qauthz_pam_is_allowed(QAuthZ *authz,
> +                                  const char *identity,
> +                                  Error **errp)
> +{
> +    QAuthZPam *pauthz = QAUTHZ_PAM(authz);
> +    const struct pam_conv pam_conversation = { 0 };
> +    pam_handle_t *pamh = NULL;
> +    int ret;
> +
> +    trace_qauthz_pam_check(authz, identity, pauthz->service);
> +    ret = pam_start(pauthz->service,
> +                    identity,
> +                    &pam_conversation,
> +                    &pamh);
> +    if (ret != PAM_SUCCESS) {
> +        error_setg(errp, "Unable to start PAM transaction: %s",
> +                   pam_strerror(NULL, ret));
> +        return false;
> +    }
> +
> +    ret = pam_acct_mgmt(pamh, PAM_SILENT);
> +    if (ret != PAM_SUCCESS) {
> +        error_setg(errp, "Unable to authorize user '%s': %s",
> +                   identity, pam_strerror(pamh, ret));
> +        goto cleanup;
> +    }
> +
> + cleanup:
> +    pam_end(pamh, ret);
> +    return ret == PAM_SUCCESS;
> +}
> +
> +
> +static void
> +qauthz_pam_prop_set_service(Object *obj,
> +                            const char *service,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
> +
> +    g_free(pauthz->service);
> +    pauthz->service = g_strdup(service);
> +}
> +
> +
> +static char *
> +qauthz_pam_prop_get_service(Object *obj,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
> +
> +    return g_strdup(pauthz->service);
> +}
> +
> +
> +static void
> +qauthz_pam_complete(UserCreatable *uc, Error **errp)
> +{
> +}
> +
> +
> +static void
> +qauthz_pam_finalize(Object *obj)
> +{
> +    QAuthZPam *pauthz = QAUTHZ_PAM(obj);
> +
> +    g_free(pauthz->service);
> +}
> +
> +
> +static void
> +qauthz_pam_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    ucc->complete = qauthz_pam_complete;
> +    authz->is_allowed = qauthz_pam_is_allowed;
> +
> +    object_class_property_add_str(oc, "service",
> +                                  qauthz_pam_prop_get_service,
> +                                  qauthz_pam_prop_set_service,
> +                                  NULL);
> +}
> +
> +
> +QAuthZPam *qauthz_pam_new(const char *id,
> +                          const char *service,
> +                          Error **errp)
> +{
> +    return QAUTHZ_PAM(
> +        object_new_with_props(TYPE_QAUTHZ_PAM,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "service", service,
> +                              NULL));
> +}
> +
> +
> +static const TypeInfo qauthz_pam_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_PAM,
> +    .instance_size = sizeof(QAuthZPam),
> +    .instance_finalize = qauthz_pam_finalize,
> +    .class_size = sizeof(QAuthZPamClass),
> +    .class_init = qauthz_pam_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_pam_register_types(void)
> +{
> +    type_register_static(&qauthz_pam_info);
> +}
> +
> +
> +type_init(qauthz_pam_register_types);
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 8351bf181d..ed7b273596 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -2,3 +2,6 @@ authz-obj-y += base.o
>  authz-obj-y += simple.o
>  authz-obj-y += list.o
>  authz-obj-y += listfile.o
> +authz-obj-$(CONFIG_AUTH_PAM) += pamacct.o
> +
> +pamacct.o-libs = -lpam
> diff --git a/authz/trace-events b/authz/trace-events
> index fb65349a90..72c411927d 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -13,3 +13,6 @@ qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ
>  # auth/listfile.c
>  qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
>  qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
> +
> +# auth/pam.c
> +qauthz_pam_check(void *authz, const char *identity, const char *service) "AuthZ PAM %p identity=%s service=%s"
> diff --git a/qemu-options.hx b/qemu-options.hx
> index a1c3e0e59c..a9654b8115 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4447,6 +4447,41 @@ would look like:
>       ...
>  @end example
>
> +@item -object authz-pam,id=@var{id},service=@var{string}
> +
> +Create an authorization object that will control access to network services.
> +
> +The @option{service} parameter provides the name of a PAM service to use
> +for authorization. It requires that a file @code{/etc/pam.d/@var{service}}
> +exist to provide the configuration for the @code{account} subsystem.
> +
> +An example authorization object to validate a TLS x509 distinguished
> +name would look like:
> +
> +@example
> + # $QEMU \
> +     ...
> +     -object authz-simple,id=auth0,service=qemu-vnc

oops, wrong example,

other than that,
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> +     ...
> +@end example
> +
> +There would then be a corresponding config file for PAM at
> +@code{/etc/pam.d/qemu-vnc} that contains:
> +
> +@example
> +account requisite  pam_listfile.so item=user sense=allow \
> +           file=/etc/qemu/vnc.allow
> +@end example
> +
> +Finally the @code{/etc/qemu/vnc.allow} file would contain
> +the list of x509 distingished names that are permitted
> +access
> +
> +@example
> +CN=laptop.example.com,O=Example Home,L=London,ST=London,C=GB
> +@end example
> +
> +
>  @end table
>
>  ETEXI
> --
> 2.17.2
>
>


--
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file access control list
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file " Daniel P. Berrangé
  2018-10-22 23:56   ` Philippe Mathieu-Daudé
@ 2018-11-07 22:23   ` Marc-André Lureau
  2018-11-15 10:33     ` Daniel P. Berrangé
  1 sibling, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 22:23 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:42 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> Add a QAuthZListFile object type that implements the QAuthZ interface. This
> built-in implementation is a proxy around the QAtuhZList object type,
> initializing it from an external file, and optionally, automatically
> reloading it whenever it changes.
>
> To create an instance of this object via the QMP monitor, the syntax
> used would be:
>
>       {
>         "execute": "object-add",
>         "arguments": {
>           "qom-type": "authz-list-file",
>           "id": "authz0",
>           "parameters": {
>             "filename": "/etc/qemu/vnc.acl",
>             "refresh": "yes"
>           }
>         }
>       }
>
> If "refresh" is "yes", inotify is used to monitor the file,
> automatically reloading changes. If an error occurs during reloading,
> all authorizations will fail until the file is next successfully
> loaded.
>
> The /etc/qemu/vnc.acl file would contain a JSON representation of a
> QAuthZList object
>
>     {
>       "rules": [
>          { "match": "fred", "policy": "allow", "format": "exact" },
>          { "match": "bob", "policy": "allow", "format": "exact" },
>          { "match": "danb", "policy": "deny", "format": "glob" },
>          { "match": "dan*", "policy": "allow", "format": "exact" },
>       ],
>       "policy": "deny"
>     }
>
> This sets up an authorization rule that allows 'fred', 'bob' and anyone
> whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> denied.
>
> The object can be loaded on the comand line using
>
>    -object authz-list-file,id=authz0,filename=/etc/qemu/vnc.acl,refresh=yes
>
> Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> ---
>  include/authz/listfile.h | 110 +++++++++++++++
>  authz/listfile.c         | 286 +++++++++++++++++++++++++++++++++++++++
>  authz/Makefile.objs      |   1 +
>  authz/trace-events       |   4 +
>  qemu-options.hx          |  46 +++++++
>  5 files changed, 447 insertions(+)
>  create mode 100644 include/authz/listfile.h
>  create mode 100644 authz/listfile.c
>
> diff --git a/include/authz/listfile.h b/include/authz/listfile.h
> new file mode 100644
> index 0000000000..244aadc064
> --- /dev/null
> +++ b/include/authz/listfile.h
> @@ -0,0 +1,110 @@
> +/*
> + * QEMU list file authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_LIST_FILE_H__
> +#define QAUTHZ_LIST_FILE_H__
> +
> +#include "authz/list.h"
> +#include "qapi/qapi-types-authz.h"
> +#include "qemu/filemonitor.h"
> +
> +#define TYPE_QAUTHZ_LIST_FILE "authz-list-file"
> +
> +#define QAUTHZ_LIST_FILE_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZListFileClass, (klass),        \
> +                       TYPE_QAUTHZ_LIST_FILE)
> +#define QAUTHZ_LIST_FILE_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZListFileClass, (obj),    \
> +                      TYPE_QAUTHZ_LIST_FILE)
> +#define QAUTHZ_LIST_FILE(obj) \
> +    INTERFACE_CHECK(QAuthZListFile, (obj),          \
> +                    TYPE_QAUTHZ_LIST_FILE)
> +
> +typedef struct QAuthZListFile QAuthZListFile;
> +typedef struct QAuthZListFileClass QAuthZListFileClass;
> +
> +
> +/**
> + * QAuthZListFile:
> + *
> + * This authorization driver provides a file mechanism
> + * for granting access by matching user names against a
> + * file of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-list-file",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "filename": "/etc/qemu/myvm-vnc.acl",
> + *        "refresh": "yes"
> + *      }
> + *    }
> + *  }
> + *
> + * If 'refresh' is 'yes', inotify is used to monitor for changes
> + * to the file and auto-reload the rules.
> + *
> + * The myvm-vnc.acl file should contain the parameters for
> + * the QAuthZList object in JSON format:
> + *
> + *      {
> + *        "rules": [
> + *           { "match": "fred", "policy": "allow", "format": "exact" },
> + *           { "match": "bob", "policy": "allow", "format": "exact" },
> + *           { "match": "danb", "policy": "deny", "format": "exact" },
> + *           { "match": "dan*", "policy": "allow", "format": "glob" }
> + *        ],
> + *        "policy": "deny"
> + *      }
> + *
> + * The object can be created on the command line using
> + *
> + *   -object authz-list-file,id=authz0,\
> + *           filename=/etc/qemu/myvm-vnc.acl,refresh=yes
> + *
> + */
> +struct QAuthZListFile {
> +    QAuthZ parent_obj;
> +
> +    QAuthZ *list;
> +    char *filename;
> +    bool refresh;
> +    QFileMonitor *file_monitor;
> +    int file_watch;
> +};
> +
> +
> +struct QAuthZListFileClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZListFile *qauthz_list_file_new(const char *id,
> +                                     const char *filename,
> +                                     Error **errp);
> +
> +
> +#endif /* QAUTHZ_LIST_FILE_H__ */
> +
> diff --git a/authz/listfile.c b/authz/listfile.c
> new file mode 100644
> index 0000000000..784bec2b1d
> --- /dev/null
> +++ b/authz/listfile.c
> @@ -0,0 +1,286 @@
> +/*
> + * QEMU access control list file authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/listfile.h"
> +#include "authz/trace.h"
> +#include "qemu/error-report.h"
> +#include "qemu/main-loop.h"
> +#include "qemu/sockets.h"
> +#include "qemu/filemonitor.h"
> +#include "qom/object_interfaces.h"
> +#include "qapi/qapi-visit-authz.h"
> +#include "qapi/qmp/qjson.h"
> +#include "qapi/qmp/qobject.h"
> +#include "qapi/qmp/qerror.h"
> +#include "qapi/qobject-input-visitor.h"
> +
> +
> +static bool
> +qauthz_list_file_is_allowed(QAuthZ *authz,
> +                            const char *identity,
> +                            Error **errp)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(authz);
> +    if (fauthz->list) {
> +        return qauthz_is_allowed(fauthz->list, identity, errp);
> +    }
> +
> +    return false;
> +}
> +
> +
> +static QAuthZ *
> +qauthz_list_file_load(QAuthZListFile *fauthz, Error **errp)
> +{
> +    GError *err = NULL;
> +    gchar *content = NULL;
> +    gsize len;
> +    QObject *obj = NULL;
> +    QDict *pdict;
> +    Visitor *v = NULL;
> +    QAuthZ *ret = NULL;
> +
> +    trace_qauthz_list_file_load(fauthz, fauthz->filename);
> +    if (!g_file_get_contents(fauthz->filename, &content, &len, &err)) {
> +        error_setg(errp, "Unable to read '%s': %s",
> +                   fauthz->filename, err->message);
> +        goto cleanup;
> +    }
> +
> +    obj = qobject_from_json(content, errp);
> +    if (!obj) {
> +        goto cleanup;
> +    }
> +
> +    pdict = qobject_to(QDict, obj);
> +    if (!pdict) {
> +        error_setg(errp, QERR_INVALID_PARAMETER_TYPE, "obj", "dict");
> +        goto cleanup;
> +    }
> +
> +    v = qobject_input_visitor_new(obj);
> +
> +    ret = (QAuthZ *)user_creatable_add_type(TYPE_QAUTHZ_LIST,
> +                                            NULL, pdict, v, errp);
> +
> + cleanup:
> +    visit_free(v);
> +    qobject_unref(obj);
> +    if (err) {
> +        g_error_free(err);
> +    }
> +    g_free(content);
> +    return ret;
> +}
> +
> +
> +static void
> +qauthz_list_file_event(int wd G_GNUC_UNUSED,
> +                       QFileMonitorEvent ev G_GNUC_UNUSED,
> +                       const char *name G_GNUC_UNUSED,
> +                       void *opaque)
> +{
> +    QAuthZListFile *fauthz = opaque;
> +    Error *err = NULL;
> +
> +    if (ev != QFILE_MONITOR_EVENT_MODIFIED &&
> +        ev != QFILE_MONITOR_EVENT_CREATED) {
> +        return;
> +    }
> +
> +    object_unref(OBJECT(fauthz->list));
> +    fauthz->list = qauthz_list_file_load(fauthz, &err);
> +    trace_qauthz_list_file_refresh(fauthz,
> +                                   fauthz->filename, fauthz->list ? 1 : 0);
> +    if (!fauthz->list) {
> +        error_report_err(err);
> +    }
> +}
> +
> +static void
> +qauthz_list_file_complete(UserCreatable *uc, Error **errp)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(uc);
> +    gchar *dir = NULL, *file = NULL;
> +
> +    fauthz->list = qauthz_list_file_load(fauthz, errp);
> +
> +    if (!fauthz->refresh) {
> +        return;
> +    }
> +
> +    fauthz->file_monitor = qemu_file_monitor_get_instance(errp);
> +    if (!fauthz->file_monitor) {
> +        return;
> +    }
> +
> +    dir = g_path_get_dirname(fauthz->filename);
> +    if (g_str_equal(dir, ".")) {
> +        error_setg(errp, "Filename must be an absolute path");
> +        goto cleanup;
> +    }
> +    file = g_path_get_basename(fauthz->filename);
> +    if (g_str_equal(file, ".")) {
> +        error_setg(errp, "Path has no trailing filename component");
> +        goto cleanup;
> +    }
> +
> +    fauthz->file_watch = qemu_file_monitor_add_watch(
> +        fauthz->file_monitor, dir, file,
> +        qauthz_list_file_event, fauthz, errp);
> +    if (fauthz->file_watch < 0) {
> +        goto cleanup;
> +    }
> +
> + cleanup:
> +    g_free(file);
> +    g_free(dir);
> +}
> +
> +
> +static void
> +qauthz_list_file_prop_set_filename(Object *obj,
> +                                   const char *value,
> +                                   Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    fauthz->filename = g_strdup(value);

Either prevent from modifying the filename, or free the exisiting value.

other than that (and the lack of test)
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>


> +}
> +
> +
> +static char *
> +qauthz_list_file_prop_get_filename(Object *obj,
> +                                   Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    return g_strdup(fauthz->filename);
> +}
> +
> +
> +static void
> +qauthz_list_file_prop_set_refresh(Object *obj,
> +                                  bool value,
> +                                  Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    fauthz->refresh = value;
> +}
> +
> +
> +static bool
> +qauthz_list_file_prop_get_refresh(Object *obj,
> +                                  Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    return fauthz->refresh;
> +}
> +
> +
> +static void
> +qauthz_list_file_finalize(Object *obj)
> +{
> +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> +
> +    if (fauthz->file_watch != -1 && fauthz->file_monitor) {
> +        gchar *dir = g_path_get_dirname(fauthz->filename);
> +        qemu_file_monitor_remove_watch(fauthz->file_monitor,
> +                                       dir,
> +                                       fauthz->file_watch);
> +        g_free(dir);
> +    }
> +    object_unref(OBJECT(fauthz->list));
> +    g_free(fauthz->filename);
> +}
> +
> +
> +static void
> +qauthz_list_file_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    ucc->complete = qauthz_list_file_complete;
> +
> +    object_class_property_add_str(oc, "filename",
> +                                  qauthz_list_file_prop_get_filename,
> +                                  qauthz_list_file_prop_set_filename,
> +                                  NULL);
> +    object_class_property_add_bool(oc, "refresh",
> +                                   qauthz_list_file_prop_get_refresh,
> +                                   qauthz_list_file_prop_set_refresh,
> +                                   NULL);
> +
> +    authz->is_allowed = qauthz_list_file_is_allowed;
> +}
> +
> +
> +static void
> +qauthz_list_file_init(Object *obj)
> +{
> +    QAuthZListFile *authz = QAUTHZ_LIST_FILE(obj);
> +
> +    authz->file_watch = -1;
> +#ifdef CONFIG_INOTIFY1
> +    authz->refresh = TRUE;
> +#endif
> +}
> +
> +
> +QAuthZListFile *qauthz_list_file_new(const char *id,
> +                                     const char *filename,
> +                                     Error **errp)
> +{
> +    return QAUTHZ_LIST_FILE(
> +        object_new_with_props(TYPE_QAUTHZ_LIST_FILE,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "filename", filename,
> +                              NULL));
> +}
> +
> +
> +static const TypeInfo qauthz_list_file_info = {
> +    .parent = TYPE_QAUTHZ_LIST,
> +    .name = TYPE_QAUTHZ_LIST_FILE,
> +    .instance_init = qauthz_list_file_init,
> +    .instance_size = sizeof(QAuthZListFile),
> +    .instance_finalize = qauthz_list_file_finalize,
> +    .class_size = sizeof(QAuthZListFileClass),
> +    .class_init = qauthz_list_file_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_list_file_register_types(void)
> +{
> +    type_register_static(&qauthz_list_file_info);
> +}
> +
> +
> +type_init(qauthz_list_file_register_types);
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 921fa624d7..8351bf181d 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1,3 +1,4 @@
>  authz-obj-y += base.o
>  authz-obj-y += simple.o
>  authz-obj-y += list.o
> +authz-obj-y += listfile.o
> diff --git a/authz/trace-events b/authz/trace-events
> index a896d876e8..fb65349a90 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -9,3 +9,7 @@ qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *goti
>  # auth/list.c
>  qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
>  qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
> +
> +# auth/listfile.c
> +qauthz_list_file_load(void *authz, const char *filename) "AuthZ file %p load filename=%s"
> +qauthz_list_file_refresh(void *authz, const char *filename, int success) "AuthZ file %p load filename=%s success=%d"
> diff --git a/qemu-options.hx b/qemu-options.hx
> index 68eaf39cc4..a1c3e0e59c 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4401,6 +4401,52 @@ would look like:
>  Note the use of quotes due to the x509 distinguished name containing
>  whitespace, and escaping of ','.
>
> +@item -object authz-listfile,id=@var{id},filename=@var{path},refresh=@var{yes|no}
> +
> +Create an authorization object that will control access to network services.
> +
> +The @option{filename} parameter is the fully qualified path to a file
> +containing the access control list rules in JSON format.
> +
> +An example set of rules that match against SASL usernames might look
> +like:
> +
> +@example
> +  @{
> +    "rules": [
> +       @{ "match": "fred", "policy": "allow", "format": "exact" @},
> +       @{ "match": "bob", "policy": "allow", "format": "exact" @},
> +       @{ "match": "danb", "policy": "deny", "format": "glob" @},
> +       @{ "match": "dan*", "policy": "allow", "format": "exact" @},
> +    ],
> +    "policy": "deny"
> +  @}
> +@end example
> +
> +When checking access the object will iterate over all the rules and
> +the first rule to match will have its @option{policy} value returned
> +as the result. If no rules match, then the default @option{policy}
> +value is returned.
> +
> +The rules can either be an exact string match, or they can use the
> +simple UNIX glob pattern matching to allow wildcards to be used.
> +
> +If @option{refresh} is set to true the file will be monitored
> +and automatically reloaded whenever its content changes.
> +
> +As with the @code{authz-simple} object, the format of the identity
> +strings being matched depends on the network service, but is usually
> +a TLS x509 distinguished name, or a SASL username.
> +
> +An example authorization object to validate a SASL username
> +would look like:
> +@example
> + # $QEMU \
> +     ...
> +     -object authz-simple,id=auth0,filename=/etc/qemu/vnc-sasl.acl,refresh=yes
> +     ...
> +@end example
> +
>  @end table
>
>  ETEXI
> --
> 2.17.2
>
>


--
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
  2018-10-23 10:18   ` Philippe Mathieu-Daudé
@ 2018-11-07 22:23   ` Marc-André Lureau
  2018-11-07 22:38     ` Eric Blake
  2018-11-13 17:29     ` Daniel P. Berrangé
  2018-11-08  8:18   ` Marc-André Lureau
  2 siblings, 2 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 22:23 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

Hi

On Fri, Oct 19, 2018 at 5:45 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> Add a QAuthZList object type that implements the QAuthZ interface. This
> built-in implementation maintains a trivial access control list with a
> sequence of match rules and a final default policy. This replicates the
> functionality currently provided by the qemu_acl module.
>
> To create an instance of this object via the QMP monitor, the syntax
> used would be:
>
>   {
>     "execute": "object-add",
>     "arguments": {
>       "qom-type": "authz-list",
>       "id": "authz0",
>       "parameters": {
>         "rules": [
>            { "match": "fred", "policy": "allow", "format": "exact" },
>            { "match": "bob", "policy": "allow", "format": "exact" },
>            { "match": "danb", "policy": "deny", "format": "glob" },
>            { "match": "dan*", "policy": "allow", "format": "exact" },
>         ],
>         "policy": "deny"
>       }
>     }
>   }
>
> This sets up an authorization rule that allows 'fred', 'bob' and anyone
> whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> denied.
>
> It is not currently possible to create this via -object, since there is
> no syntax supported to specify non-scalar properties for objects. This
> is likely to be addressed by later support for using JSON with -object,
> or an equivalent approach.
>
> In any case the future "authz-listfile" object can be used from the
> CLI and is likely a better choice, as it allows the ACL to be refreshed
> automatically on change.
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

some notes below, but in general:
Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>


> ---
>  Makefile                |   7 +-
>  Makefile.objs           |   4 +
>  qapi/authz.json         |  58 ++++++++
>  qapi/qapi-schema.json   |   1 +
>  include/authz/list.h    | 106 ++++++++++++++
>  authz/list.c            | 309 ++++++++++++++++++++++++++++++++++++++++
>  tests/test-authz-list.c | 171 ++++++++++++++++++++++
>  .gitignore              |   4 +
>  MAINTAINERS             |   1 +
>  authz/Makefile.objs     |   1 +
>  authz/trace-events      |   4 +
>  tests/Makefile.include  |   4 +
>  12 files changed, 669 insertions(+), 1 deletion(-)
>  create mode 100644 qapi/authz.json
>  create mode 100644 include/authz/list.h
>  create mode 100644 authz/list.c
>  create mode 100644 tests/test-authz-list.c
>
> diff --git a/Makefile b/Makefile
> index 4b20ee2b19..da9ea40725 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -600,7 +600,8 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>                 $(SRC_PATH)/qapi/tpm.json \
>                 $(SRC_PATH)/qapi/trace.json \
>                 $(SRC_PATH)/qapi/transaction.json \
> -               $(SRC_PATH)/qapi/ui.json
> +               $(SRC_PATH)/qapi/ui.json \
> +               $(SRC_PATH)/qapi/authz.json
>
>  qapi/qapi-builtin-types.c qapi/qapi-builtin-types.h \
>  qapi/qapi-types.c qapi/qapi-types.h \
> @@ -621,6 +622,7 @@ qapi/qapi-types-tpm.c qapi/qapi-types-tpm.h \
>  qapi/qapi-types-trace.c qapi/qapi-types-trace.h \
>  qapi/qapi-types-transaction.c qapi/qapi-types-transaction.h \
>  qapi/qapi-types-ui.c qapi/qapi-types-ui.h \
> +qapi/qapi-types-authz.c qapi/qapi-types-authz.h \
>  qapi/qapi-builtin-visit.c qapi/qapi-builtin-visit.h \
>  qapi/qapi-visit.c qapi/qapi-visit.h \
>  qapi/qapi-visit-block-core.c qapi/qapi-visit-block-core.h \
> @@ -640,6 +642,7 @@ qapi/qapi-visit-tpm.c qapi/qapi-visit-tpm.h \
>  qapi/qapi-visit-trace.c qapi/qapi-visit-trace.h \
>  qapi/qapi-visit-transaction.c qapi/qapi-visit-transaction.h \
>  qapi/qapi-visit-ui.c qapi/qapi-visit-ui.h \
> +qapi/qapi-visit-authz.c qapi/qapi-visit-authz.h \
>  qapi/qapi-commands.h qapi/qapi-commands.c \
>  qapi/qapi-commands-block-core.c qapi/qapi-commands-block-core.h \
>  qapi/qapi-commands-block.c qapi/qapi-commands-block.h \
> @@ -658,6 +661,7 @@ qapi/qapi-commands-tpm.c qapi/qapi-commands-tpm.h \
>  qapi/qapi-commands-trace.c qapi/qapi-commands-trace.h \
>  qapi/qapi-commands-transaction.c qapi/qapi-commands-transaction.h \
>  qapi/qapi-commands-ui.c qapi/qapi-commands-ui.h \
> +qapi/qapi-commands-authz.c qapi/qapi-commands-authz.h \
>  qapi/qapi-events.c qapi/qapi-events.h \
>  qapi/qapi-events-block-core.c qapi/qapi-events-block-core.h \
>  qapi/qapi-events-block.c qapi/qapi-events-block.h \
> @@ -676,6 +680,7 @@ qapi/qapi-events-tpm.c qapi/qapi-events-tpm.h \
>  qapi/qapi-events-trace.c qapi/qapi-events-trace.h \
>  qapi/qapi-events-transaction.c qapi/qapi-events-transaction.h \
>  qapi/qapi-events-ui.c qapi/qapi-events-ui.h \
> +qapi/qapi-events-authz.c qapi/qapi-events-authz.h \
>  qapi/qapi-introspect.h qapi/qapi-introspect.c \
>  qapi/qapi-doc.texi: \
>  qapi-gen-timestamp ;
> diff --git a/Makefile.objs b/Makefile.objs
> index ecb1071c4f..825c5863ac 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -21,6 +21,7 @@ util-obj-y += qapi/qapi-types-tpm.o
>  util-obj-y += qapi/qapi-types-trace.o
>  util-obj-y += qapi/qapi-types-transaction.o
>  util-obj-y += qapi/qapi-types-ui.o
> +util-obj-y += qapi/qapi-types-authz.o
>  util-obj-y += qapi/qapi-builtin-visit.o
>  util-obj-y += qapi/qapi-visit.o
>  util-obj-y += qapi/qapi-visit-block-core.o
> @@ -40,6 +41,7 @@ util-obj-y += qapi/qapi-visit-tpm.o
>  util-obj-y += qapi/qapi-visit-trace.o
>  util-obj-y += qapi/qapi-visit-transaction.o
>  util-obj-y += qapi/qapi-visit-ui.o
> +util-obj-y += qapi/qapi-visit-authz.o
>  util-obj-y += qapi/qapi-events.o
>  util-obj-y += qapi/qapi-events-block-core.o
>  util-obj-y += qapi/qapi-events-block.o
> @@ -58,6 +60,7 @@ util-obj-y += qapi/qapi-events-tpm.o
>  util-obj-y += qapi/qapi-events-trace.o
>  util-obj-y += qapi/qapi-events-transaction.o
>  util-obj-y += qapi/qapi-events-ui.o
> +util-obj-y += qapi/qapi-events-authz.o
>  util-obj-y += qapi/qapi-introspect.o
>
>  chardev-obj-y = chardev/
> @@ -160,6 +163,7 @@ common-obj-y += qapi/qapi-commands-tpm.o
>  common-obj-y += qapi/qapi-commands-trace.o
>  common-obj-y += qapi/qapi-commands-transaction.o
>  common-obj-y += qapi/qapi-commands-ui.o
> +common-obj-y += qapi/qapi-commands-authz.o
>  common-obj-y += qapi/qapi-introspect.o
>  common-obj-y += qmp.o hmp.o
>  endif
> diff --git a/qapi/authz.json b/qapi/authz.json
> new file mode 100644
> index 0000000000..607839c627
> --- /dev/null
> +++ b/qapi/authz.json
> @@ -0,0 +1,58 @@
> +# -*- Mode: Python -*-
> +#
> +# QAPI authz definitions
> +
> +##
> +# @QAuthZListPolicy:
> +#
> +# The authorization policy result
> +#
> +# @deny: deny access
> +# @allow: allow access
> +#
> +# Since: 3.0

obviously, you'll have to update to 3.2

> +##
> +{ 'enum': 'QAuthZListPolicy',
> +  'prefix': 'QAUTHZ_LIST_POLICY',
> +  'data': ['deny', 'allow']}
> +
> +##
> +# @QAuthZListFormat:
> +#
> +# The authorization policy result
> +#
> +# @exact: an exact string match
> +# @glob: string with ? and * shell wildcard support
> +#
> +# Since: 3.0
> +##
> +{ 'enum': 'QAuthZListFormat',
> +  'prefix': 'QAUTHZ_LIST_FORMAT',
> +  'data': ['exact', 'glob']}
> +
> +##
> +# @QAuthZListRule:
> +#
> +# A single authorization rule.
> +#
> +# @match: a glob to match against a user identity
> +# @policy: the result to return if @match evaluates to true
> +# @format: (optional) the format of the @match rule (default 'exact')
> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRule',
> +  'data': {'match': 'str',
> +           'policy': 'QAuthZListPolicy',
> +           '*format': 'QAuthZListFormat'}}
> +
> +##
> +# @QAuthZListRuleListHack:
> +#
> +# Not exposed via QMP; hack to generate QAuthZListRuleList
> +# for use internally by the code.

Well, this will probably end in the documentation (it's already in the
.json, which is one source of documentation ;).

What about adding a 'gen-list' field, or a 'pragma' listing the
structs that should have list code generated?

> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRuleListHack',
> +  'data': { 'unused': ['QAuthZListRule'] } }
> diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
> index 65b6dc2f6f..6a5a02a388 100644
> --- a/qapi/qapi-schema.json
> +++ b/qapi/qapi-schema.json
> @@ -89,6 +89,7 @@
>  { 'include': 'rocker.json' }
>  { 'include': 'tpm.json' }
>  { 'include': 'ui.json' }
> +{ 'include': 'authz.json' }
>  { 'include': 'migration.json' }
>  { 'include': 'transaction.json' }
>  { 'include': 'trace.json' }
> diff --git a/include/authz/list.h b/include/authz/list.h
> new file mode 100644
> index 0000000000..eb131ba0f0
> --- /dev/null
> +++ b/include/authz/list.h
> @@ -0,0 +1,106 @@
> +/*
> + * QEMU list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_LIST_H__
> +#define QAUTHZ_LIST_H__
> +
> +#include "authz/base.h"
> +#include "qapi/qapi-types-authz.h"
> +
> +#define TYPE_QAUTHZ_LIST "authz-list"
> +
> +#define QAUTHZ_LIST_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZListClass, (klass),        \
> +                       TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZListClass, (obj),    \
> +                      TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST(obj) \
> +    INTERFACE_CHECK(QAuthZList, (obj),          \
> +                    TYPE_QAUTHZ_LIST)
> +
> +typedef struct QAuthZList QAuthZList;
> +typedef struct QAuthZListClass QAuthZListClass;
> +
> +
> +/**
> + * QAuthZList:
> + *
> + * This authorization driver provides a list mechanism
> + * for granting access by matching user names against a
> + * list of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-list",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "rules": [
> + *           { "match": "fred", "policy": "allow", "format": "exact" },
> + *           { "match": "bob", "policy": "allow", "format": "exact" },
> + *           { "match": "danb", "policy": "deny", "format": "exact" },
> + *           { "match": "dan*", "policy": "allow", "format": "glob" }
> + *        ],
> + *        "policy": "deny"
> + *      }
> + *    }
> + *  }
> + *
> + */
> +struct QAuthZList {
> +    QAuthZ parent_obj;
> +
> +    QAuthZListPolicy policy;
> +    QAuthZListRuleList *rules;
> +};
> +
> +
> +struct QAuthZListClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp);
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth,
> +                                const char *match);
> +
> +
> +#endif /* QAUTHZ_LIST_H__ */
> +
> diff --git a/authz/list.c b/authz/list.c
> new file mode 100644
> index 0000000000..1d9544681c
> --- /dev/null
> +++ b/authz/list.c
> @@ -0,0 +1,309 @@
> +/*
> + * QEMU access control list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/list.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +#include "qapi/qapi-visit-authz.h"
> +
> +#ifdef CONFIG_FNMATCH
> +#include <fnmatch.h>
> +#endif
> +
> +static bool qauthz_list_is_allowed(QAuthZ *authz,
> +                                   const char *identity,
> +                                   Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(authz);
> +    QAuthZListRuleList *rules = lauthz->rules;
> +
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        QAuthZListFormat format = rule->has_format ? rule->format :
> +            QAUTHZ_LIST_FORMAT_EXACT;
> +
> +        trace_qauthz_list_check_rule(authz, rule->match, identity,
> +                                     format, rule->policy);
> +        switch (format) {
> +        case QAUTHZ_LIST_FORMAT_EXACT:
> +            if (strcmp(rule->match, identity) == 0) {

g_str_equal() ?

> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#ifdef CONFIG_FNMATCH
> +        case QAUTHZ_LIST_FORMAT_GLOB:
> +            if (fnmatch(rule->match, identity, 0) == 0) {

Would GPatternSpec be a good alternative?

> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#else
> +            return false;
> +#endif
> +        default:


No g_warn_if_reached() ?Then perhaps add a comment why.


> +            return false;
> +        }
> +        rules = rules->next;
> +    }
> +
> +    trace_qauthz_list_default_policy(authz, identity, lauthz->policy);
> +    return lauthz->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +}
> +
> +
> +static void
> +qauthz_list_prop_set_policy(Object *obj,
> +                            int value,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    lauthz->policy = value;
> +}
> +
> +
> +static int
> +qauthz_list_prop_get_policy(Object *obj,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    return lauthz->policy;
> +}
> +
> +
> +static void
> +qauthz_list_prop_get_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +}
> +
> +static void
> +qauthz_list_prop_set_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +    QAuthZListRuleList *oldrules;
> +#ifndef CONFIG_FNMATCH
> +    QAuthZListRuleList *rules;
> +#endif
> +
> +    oldrules = lauthz->rules;
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +
> +#ifndef CONFIG_FNMATCH
> +    rules = lauthz->rules;
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        if (rule->has_format &&
> +            rule->format == QAUTHZ_LIST_FORMAT_GLOB) {
> +            error_setg(errp, "Glob format not supported on this platform");
> +            qapi_free_QAuthZListRuleList(lauthz->rules);
> +            lauthz->rules = oldrules;
> +            return;
> +        }
> +    }
> +#endif
> +
> +    qapi_free_QAuthZListRuleList(oldrules);
> +}
> +
> +
> +static void
> +qauthz_list_finalize(Object *obj)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    qapi_free_QAuthZListRuleList(lauthz->rules);
> +}
> +
> +
> +static void
> +qauthz_list_class_init(ObjectClass *oc, void *data)
> +{
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    object_class_property_add_enum(oc, "policy",
> +                                   "QAuthZListPolicy",
> +                                   &QAuthZListPolicy_lookup,
> +                                   qauthz_list_prop_get_policy,
> +                                   qauthz_list_prop_set_policy,
> +                                   NULL);
> +
> +    object_class_property_add(oc, "rules", "QAuthZListRule",
> +                              qauthz_list_prop_get_rules,
> +                              qauthz_list_prop_set_rules,
> +                              NULL, NULL, NULL);
> +
> +    authz->is_allowed = qauthz_list_is_allowed;
> +}
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp)
> +{
> +    return QAUTHZ_LIST(
> +        object_new_with_props(TYPE_QAUTHZ_LIST,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "policy", QAuthZListPolicy_lookup.array[policy],
> +                              NULL));
> +}
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules) {
> +        while (rules->next) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules && index > 0) {
> +        while (rules->next && i < (index - 1)) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        tmp->next = rules->next;
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        tmp->next = auth->rules;
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *prev;
> +    size_t i = 0;
> +
> +    prev = NULL;
> +    rules = auth->rules;
> +    while (rules) {
> +        rule = rules->value;
> +        if (g_str_equal(rule->match, match)) {
> +            if (prev) {
> +                prev->next = rules->next;
> +            } else {
> +                auth->rules = rules->next;
> +            }
> +            rules->next = NULL;
> +            qapi_free_QAuthZListRuleList(rules);
> +            return i;

What's the point in returning the old index? Maybe true/false along
with an Error would be more convenient?

> +        }
> +        prev = rules;
> +        rules = rules->next;
> +        i++;
> +    }
> +
> +    return -1;
> +}
> +
> +
> +static const TypeInfo qauthz_list_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_LIST,
> +    .instance_size = sizeof(QAuthZList),
> +    .instance_finalize = qauthz_list_finalize,
> +    .class_size = sizeof(QAuthZListClass),
> +    .class_init = qauthz_list_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_list_register_types(void)
> +{
> +    type_register_static(&qauthz_list_info);
> +}
> +
> +
> +type_init(qauthz_list_register_types);
> diff --git a/tests/test-authz-list.c b/tests/test-authz-list.c
> new file mode 100644
> index 0000000000..02ce4c5763
> --- /dev/null
> +++ b/tests/test-authz-list.c
> @@ -0,0 +1,171 @@
> +/*
> + * QEMU list authorization object
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "authz/list.h"
> +
> +static void test_authz_default_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_default_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +
> +static void test_authz_complex(void)
> +{
> +#ifndef CONFIG_FNMATCH
> +    Error *local_err = NULL;
> +#endif
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +#ifdef CONFIG_FNMATCH
> +    qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
> +#else
> +    g_assert(qauthz_list_append_rule(auth, "dan*",
> +                                     QAUTHZ_LIST_POLICY_ALLOW,
> +                                     QAUTHZ_LIST_FORMAT_GLOB,
> +                                     &local_err) < 0);
> +    g_assert(local_err != NULL);
> +    error_free(local_err);
> +#endif
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_add_remove(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 0);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 1);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 2);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 3);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
> +                    ==, 2);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            2,
> +                                            &error_abort),
> +                    ==, 2);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    module_call_init(MODULE_INIT_QOM);
> +
> +    g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
> +    g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
> +    g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
> +    g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
> +    g_test_add_func("/auth/list/complex", test_authz_complex);
> +    g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
> +
> +    return g_test_run();
> +}
> diff --git a/.gitignore b/.gitignore
> index 64efdfd929..9b9fcad829 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -47,6 +47,7 @@
>  /qapi/qapi-commands-trace.[ch]
>  /qapi/qapi-commands-transaction.[ch]
>  /qapi/qapi-commands-ui.[ch]
> +/qapi/qapi-commands-authz.[ch]
>  /qapi/qapi-commands.[ch]
>  /qapi/qapi-events-block-core.[ch]
>  /qapi/qapi-events-block.[ch]
> @@ -65,6 +66,7 @@
>  /qapi/qapi-events-trace.[ch]
>  /qapi/qapi-events-transaction.[ch]
>  /qapi/qapi-events-ui.[ch]
> +/qapi/qapi-events-authz.[ch]
>  /qapi/qapi-events.[ch]
>  /qapi/qapi-introspect.[ch]
>  /qapi/qapi-types-block-core.[ch]
> @@ -84,6 +86,7 @@
>  /qapi/qapi-types-trace.[ch]
>  /qapi/qapi-types-transaction.[ch]
>  /qapi/qapi-types-ui.[ch]
> +/qapi/qapi-types-authz.[ch]
>  /qapi/qapi-types.[ch]
>  /qapi/qapi-visit-block-core.[ch]
>  /qapi/qapi-visit-block.[ch]
> @@ -102,6 +105,7 @@
>  /qapi/qapi-visit-trace.[ch]
>  /qapi/qapi-visit-transaction.[ch]
>  /qapi/qapi-visit-ui.[ch]
> +/qapi/qapi-visit-authz.[ch]
>  /qapi/qapi-visit.[ch]
>  /qapi/qapi-doc.texi
>  /qemu-doc.html
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9624734923..5d33cf4605 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1861,6 +1861,7 @@ User authorization
>  M: Daniel P. Berrange <berrange@redhat.com>
>  S: Maintained
>  F: authz/
> +F: qapi/authz.json
>  F: include/authz/
>  F: tests/test-authz-*
>
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 2a75d53840..921fa624d7 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1,2 +1,3 @@
>  authz-obj-y += base.o
>  authz-obj-y += simple.o
> +authz-obj-y += list.o
> diff --git a/authz/trace-events b/authz/trace-events
> index 1ef796c1e1..a896d876e8 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -5,3 +5,7 @@ qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p che
>
>  # auth/simple.c
>  qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
> +
> +# auth/list.c
> +qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
> +qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index 7fe8578972..b2369c14cb 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -126,6 +126,7 @@ check-unit-y += tests/test-bufferiszero$(EXESUF)
>  check-unit-y += tests/test-uuid$(EXESUF)
>  check-unit-y += tests/ptimer-test$(EXESUF)
>  check-unit-y += tests/test-qapi-util$(EXESUF)
> +check-unit-y += tests/test-authz-list$(EXESUF)
>
>  check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
>
> @@ -572,6 +573,9 @@ tests/test-timed-average$(EXESUF): tests/test-timed-average.o $(test-util-obj-y)
>  tests/test-base64$(EXESUF): tests/test-base64.o $(test-util-obj-y)
>  tests/ptimer-test$(EXESUF): tests/ptimer-test.o tests/ptimer-test-stubs.o hw/core/ptimer.o
>
> +tests/test-authz-list$(EXESUF): tests/test-authz-list.o \
> +       $(test-util-obj-y) $(authz-obj-y) $(qom-obj-y)
> +
>  tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
>
>  tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
> --
> 2.17.2
>
>


--
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks Daniel P. Berrangé
  2018-10-22 23:54   ` Philippe Mathieu-Daudé
@ 2018-11-07 22:23   ` Marc-André Lureau
  2018-11-13 17:11     ` Daniel P. Berrangé
  1 sibling, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 22:23 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> In many cases a single VM will just need to whilelist a single identity
> as the allowed user of network services. This is especially the case for
> TLS live migration (optionally with NBD storage) where we just need to
> whitelist the x509 certificate distinguished name of the source QEMU
> host.
>
> Via QMP this can be configured with:
>
>   {
>     "execute": "object-add",
>     "arguments": {
>       "qom-type": "authz-simple",
>       "id": "authz0",
>       "parameters": {
>         "identity": "fred"
>       }
>     }
>   }
>
> Or via the command line
>
>   -object authz-simple,id=authz0,identity=fred
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

(a test would be trivial)

> ---
>  include/authz/simple.h |  84 ++++++++++++++++++++++++++++++
>  authz/simple.c         | 115 +++++++++++++++++++++++++++++++++++++++++
>  authz/Makefile.objs    |   1 +
>  authz/trace-events     |   3 ++
>  qemu-options.hx        |  24 +++++++++
>  5 files changed, 227 insertions(+)
>  create mode 100644 include/authz/simple.h
>  create mode 100644 authz/simple.c
>
> diff --git a/include/authz/simple.h b/include/authz/simple.h
> new file mode 100644
> index 0000000000..4686e7676d
> --- /dev/null
> +++ b/include/authz/simple.h
> @@ -0,0 +1,84 @@
> +/*
> + * QEMU simple authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_SIMPLE_H__
> +#define QAUTHZ_SIMPLE_H__
> +
> +#include "authz/base.h"
> +
> +#define TYPE_QAUTHZ_SIMPLE "authz-simple"
> +
> +#define QAUTHZ_SIMPLE_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZSimpleClass, (klass),        \
> +                       TYPE_QAUTHZ_SIMPLE)
> +#define QAUTHZ_SIMPLE_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZSimpleClass, (obj),    \
> +                      TYPE_QAUTHZ_SIMPLE)
> +#define QAUTHZ_SIMPLE(obj) \
> +    INTERFACE_CHECK(QAuthZSimple, (obj),          \
> +                    TYPE_QAUTHZ_SIMPLE)
> +
> +typedef struct QAuthZSimple QAuthZSimple;
> +typedef struct QAuthZSimpleClass QAuthZSimpleClass;
> +
> +
> +/**
> + * QAuthZSimple:
> + *
> + * This authorization driver provides a simple mechanism
> + * for granting access based on an exact matched username.
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-simple",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "identity": "fred"
> + *      }
> + *    }
> + *  }
> + *
> + * Or via the command line
> + *
> + *   -object authz-simple,id=authz0,identity=fred
> + *
> + */
> +struct QAuthZSimple {
> +    QAuthZ parent_obj;
> +
> +    char *identity;
> +};
> +
> +
> +struct QAuthZSimpleClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZSimple *qauthz_simple_new(const char *id,
> +                                const char *identity,
> +                                Error **errp);
> +
> +
> +#endif /* QAUTHZ_SIMPLE_H__ */
> +
> diff --git a/authz/simple.c b/authz/simple.c
> new file mode 100644
> index 0000000000..8ab718803e
> --- /dev/null
> +++ b/authz/simple.c
> @@ -0,0 +1,115 @@
> +/*
> + * QEMU simple authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/simple.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +
> +static bool qauthz_simple_is_allowed(QAuthZ *authz,
> +                                     const char *identity,
> +                                     Error **errp)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(authz);
> +
> +    trace_qauthz_simple_is_allowed(authz, sauthz->identity, identity);
> +    return g_str_equal(identity, sauthz->identity);
> +}
> +
> +static void
> +qauthz_simple_prop_set_identity(Object *obj,
> +                                const char *value,
> +                                Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    g_free(sauthz->identity);
> +    sauthz->identity = g_strdup(value);
> +}
> +
> +
> +static char *
> +qauthz_simple_prop_get_identity(Object *obj,
> +                                Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    return g_strdup(sauthz->identity);
> +}
> +
> +
> +static void
> +qauthz_simple_finalize(Object *obj)
> +{
> +    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
> +
> +    g_free(sauthz->identity);
> +}
> +
> +
> +static void
> +qauthz_simple_class_init(ObjectClass *oc, void *data)
> +{
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    authz->is_allowed = qauthz_simple_is_allowed;
> +
> +    object_class_property_add_str(oc, "identity",
> +                                  qauthz_simple_prop_get_identity,
> +                                  qauthz_simple_prop_set_identity,
> +                                  NULL);
> +}
> +
> +
> +QAuthZSimple *qauthz_simple_new(const char *id,
> +                                const char *identity,
> +                                Error **errp)
> +{
> +    return QAUTHZ_SIMPLE(
> +        object_new_with_props(TYPE_QAUTHZ_SIMPLE,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "identity", identity,
> +                              NULL));
> +}
> +
> +
> +static const TypeInfo qauthz_simple_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_SIMPLE,
> +    .instance_size = sizeof(QAuthZSimple),
> +    .instance_finalize = qauthz_simple_finalize,
> +    .class_size = sizeof(QAuthZSimpleClass),
> +    .class_init = qauthz_simple_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_simple_register_types(void)
> +{
> +    type_register_static(&qauthz_simple_info);
> +}
> +
> +
> +type_init(qauthz_simple_register_types);
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 12597c9528..2a75d53840 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1 +1,2 @@
>  authz-obj-y += base.o
> +authz-obj-y += simple.o
> diff --git a/authz/trace-events b/authz/trace-events
> index 481c90f511..1ef796c1e1 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -2,3 +2,6 @@
>
>  # authz/base.c
>  qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
> +
> +# auth/simple.c
> +qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
> diff --git a/qemu-options.hx b/qemu-options.hx
> index f139459e80..68eaf39cc4 100644
> --- a/qemu-options.hx
> +++ b/qemu-options.hx
> @@ -4377,6 +4377,30 @@ e.g to launch a SEV guest
>       .....
>
>  @end example
> +
> +
> +@item -object authz-simple,id=@var{id},identity=@var{string}
> +
> +Create an authorization object that will control access to network services.
> +
> +The @option{identity} parameter is identifies the user and its format
> +depends on the network service that authorization object is associated
> +with. For authorizing based on TLS x509 certificates, the identity must
> +be the x509 distinguished name. Note that care must be taken to escape
> +any commas in the distinguished name.
> +
> +An example authorization object to validate a x509 distinguished name
> +would look like:
> +@example
> + # $QEMU \
> +     ...
> +     -object 'authz-simple,id=auth0,identity=CN=laptop.example.com,,O=Example Org,,L=London,,ST=London,,C=GB' \
> +     ...
> +@end example
> +
> +Note the use of quotes due to the x509 distinguished name containing
> +whitespace, and escaping of ','.
> +
>  @end table
>
>  ETEXI
> --
> 2.17.2
>
>


--
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class Daniel P. Berrangé
@ 2018-11-07 22:23   ` Marc-André Lureau
  0 siblings, 0 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-07 22:23 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:46 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> The current qemu_acl module provides a simple access control list
> facility inside QEMU, which is used via a set of monitor commands
> acl_show, acl_policy, acl_add, acl_remove & acl_reset.
>
> Note there is no ability to create ACLs - the network services (eg VNC
> server) were expected to create ACLs that they want to check.
>
> There is also no way to define ACLs on the command line, nor potentially
> integrate with external authorization systems like polkit, pam, ldap
> lookup, etc.
>
> The QAuthZ object defines a minimal abstract QOM class that can be
> subclassed for creating different authorization providers.
>
> Reviewed-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Tested-by: Philippe Mathieu-Daudé <philmd@redhat.com>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>

Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

> ---
>  Makefile             |  10 ++--
>  Makefile.objs        |   6 +++
>  Makefile.target      |   2 +
>  include/authz/base.h | 112 +++++++++++++++++++++++++++++++++++++++++++
>  authz/base.c         |  82 +++++++++++++++++++++++++++++++
>  MAINTAINERS          |   7 +++
>  authz/Makefile.objs  |   1 +
>  authz/trace-events   |   4 ++
>  8 files changed, 220 insertions(+), 4 deletions(-)
>  create mode 100644 include/authz/base.h
>  create mode 100644 authz/base.c
>  create mode 100644 authz/Makefile.objs
>  create mode 100644 authz/trace-events
>
> diff --git a/Makefile b/Makefile
> index f2947186a4..4b20ee2b19 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -412,6 +412,7 @@ endif
>
>  dummy := $(call unnest-vars,, \
>                  stub-obj-y \
> +                authz-obj-y \
>                  chardev-obj-y \
>                  util-obj-y \
>                  qga-obj-y \
> @@ -474,6 +475,7 @@ qemu-options.def: $(SRC_PATH)/qemu-options.hx $(SRC_PATH)/scripts/hxtool
>  SUBDIR_RULES=$(patsubst %,subdir-%, $(TARGET_DIRS))
>  SOFTMMU_SUBDIR_RULES=$(filter %-softmmu,$(SUBDIR_RULES))
>
> +$(SOFTMMU_SUBDIR_RULES): $(authz-obj-y)
>  $(SOFTMMU_SUBDIR_RULES): $(block-obj-y)
>  $(SOFTMMU_SUBDIR_RULES): $(crypto-obj-y)
>  $(SOFTMMU_SUBDIR_RULES): $(io-obj-y)
> @@ -536,9 +538,9 @@ COMMON_LDADDS = libqemuutil.a
>
>  qemu-img.o: qemu-img-cmds.h
>
> -qemu-img$(EXESUF): qemu-img.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> -qemu-nbd$(EXESUF): qemu-nbd.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> -qemu-io$(EXESUF): qemu-io.o $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> +qemu-img$(EXESUF): qemu-img.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> +qemu-nbd$(EXESUF): qemu-nbd.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> +qemu-io$(EXESUF): qemu-io.o $(authz-obj-y) $(block-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
>
>  qemu-bridge-helper$(EXESUF): qemu-bridge-helper.o $(COMMON_LDADDS)
>
> @@ -549,7 +551,7 @@ qemu-edid$(EXESUF): qemu-edid.o hw/display/edid-generate.o $(COMMON_LDADDS)
>  fsdev/virtfs-proxy-helper$(EXESUF): fsdev/virtfs-proxy-helper.o fsdev/9p-marshal.o fsdev/9p-iov-marshal.o $(COMMON_LDADDS)
>  fsdev/virtfs-proxy-helper$(EXESUF): LIBS += -lcap
>
> -scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
> +scsi/qemu-pr-helper$(EXESUF): scsi/qemu-pr-helper.o scsi/utils.o $(authz-obj-y) $(crypto-obj-y) $(io-obj-y) $(qom-obj-y) $(COMMON_LDADDS)
>  ifdef CONFIG_MPATH
>  scsi/qemu-pr-helper$(EXESUF): LIBS += -ludev -lmultipath -lmpathpersist
>  endif
> diff --git a/Makefile.objs b/Makefile.objs
> index 1e1ff387d7..ecb1071c4f 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -62,6 +62,11 @@ util-obj-y += qapi/qapi-introspect.o
>
>  chardev-obj-y = chardev/
>
> +#######################################################################
> +# authz-obj-y is code used by both qemu system emulation and qemu-img
> +
> +authz-obj-y = authz/
> +
>  #######################################################################
>  # block-obj-y is code used by both qemu system emulation and qemu-img
>
> @@ -200,6 +205,7 @@ trace-events-subdirs =
>  trace-events-subdirs += accel/kvm
>  trace-events-subdirs += accel/tcg
>  trace-events-subdirs += audio
> +trace-events-subdirs += authz
>  trace-events-subdirs += block
>  trace-events-subdirs += chardev
>  trace-events-subdirs += crypto
> diff --git a/Makefile.target b/Makefile.target
> index 4d56298bbf..c08461726d 100644
> --- a/Makefile.target
> +++ b/Makefile.target
> @@ -172,6 +172,7 @@ include $(SRC_PATH)/Makefile.objs
>  dummy := $(call unnest-vars,,target-obj-y)
>  target-obj-y-save := $(target-obj-y)
>  dummy := $(call unnest-vars,.., \
> +               authz-obj-y \
>                 block-obj-y \
>                 block-obj-m \
>                 chardev-obj-y \
> @@ -185,6 +186,7 @@ target-obj-y := $(target-obj-y-save)
>  all-obj-y += $(common-obj-y)
>  all-obj-y += $(target-obj-y)
>  all-obj-y += $(qom-obj-y)
> +all-obj-$(CONFIG_SOFTMMU) += $(authz-obj-y)
>  all-obj-$(CONFIG_SOFTMMU) += $(block-obj-y) $(chardev-obj-y)
>  all-obj-$(CONFIG_USER_ONLY) += $(crypto-aes-obj-y)
>  all-obj-$(CONFIG_SOFTMMU) += $(crypto-obj-y)
> diff --git a/include/authz/base.h b/include/authz/base.h
> new file mode 100644
> index 0000000000..77dcd54c4c
> --- /dev/null
> +++ b/include/authz/base.h
> @@ -0,0 +1,112 @@
> +/*
> + * QEMU authorization framework base class
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_BASE_H__
> +#define QAUTHZ_BASE_H__
> +
> +#include "qemu-common.h"
> +#include "qapi/error.h"
> +#include "qom/object.h"
> +
> +
> +#define TYPE_QAUTHZ "authz"
> +
> +#define QAUTHZ_CLASS(klass) \
> +     OBJECT_CLASS_CHECK(QAuthZClass, (klass), \
> +                        TYPE_QAUTHZ)
> +#define QAUTHZ_GET_CLASS(obj) \
> +     OBJECT_GET_CLASS(QAuthZClass, (obj), \
> +                      TYPE_QAUTHZ)
> +#define QAUTHZ(obj) \
> +     INTERFACE_CHECK(QAuthZ, (obj), \
> +                     TYPE_QAUTHZ)
> +
> +typedef struct QAuthZ QAuthZ;
> +typedef struct QAuthZClass QAuthZClass;
> +
> +/**
> + * QAuthZ:
> + *
> + * The QAuthZ class defines an API contract to be used
> + * for providing an authorization driver for services
> + * with user identities.
> + */
> +
> +struct QAuthZ {
> +    Object parent_obj;
> +};
> +
> +
> +struct QAuthZClass {
> +    ObjectClass parent_class;
> +
> +    bool (*is_allowed)(QAuthZ *authz,
> +                       const char *identity,
> +                       Error **errp);
> +};
> +
> +
> +/**
> + * qauthz_is_allowed:
> + * @authz: the authorization object
> + * @identity: the user identity to authorize
> + * @errp: pointer to a NULL initialized error object
> + *
> + * Check if a user @identity is authorized. If an error
> + * occurs this method will return false to indicate
> + * denial, as well as setting @errp to contain the details.
> + * Callers are recommended to treat the denial and error
> + * scenarios identically. Specifically the error info in
> + * @errp should never be fed back to the user being
> + * authorized, it is merely for benefit of administrator
> + * debugging.
> + *
> + * Returns: true if @identity is authorized, false if denied or if
> + * an error occurred.
> + */
> +bool qauthz_is_allowed(QAuthZ *authz,
> +                       const char *identity,
> +                       Error **errp);
> +
> +
> +/**
> + * qauthz_is_allowed_by_id:
> + * @authzid: ID of the authorization object
> + * @identity: the user identity to authorize
> + * @errp: pointer to a NULL initialized error object
> + *
> + * Check if a user @identity is authorized. If an error
> + * occurs this method will return false to indicate
> + * denial, as well as setting @errp to contain the details.
> + * Callers are recommended to treat the denial and error
> + * scenarios identically. Specifically the error info in
> + * @errp should never be fed back to the user being
> + * authorized, it is merely for benefit of administrator
> + * debugging.
> + *
> + * Returns: true if @identity is authorized, false if denied or if
> + * an error occurred.
> + */
> +bool qauthz_is_allowed_by_id(const char *authzid,
> +                             const char *identity,
> +                             Error **errp);
> +
> +#endif /* QAUTHZ_BASE_H__ */
> +
> diff --git a/authz/base.c b/authz/base.c
> new file mode 100644
> index 0000000000..110dfa4195
> --- /dev/null
> +++ b/authz/base.c
> @@ -0,0 +1,82 @@
> +/*
> + * QEMU authorization framework base class
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/base.h"
> +#include "authz/trace.h"
> +
> +bool qauthz_is_allowed(QAuthZ *authz,
> +                       const char *identity,
> +                       Error **errp)
> +{
> +    QAuthZClass *cls = QAUTHZ_GET_CLASS(authz);
> +    bool allowed;
> +
> +    allowed = cls->is_allowed(authz, identity, errp);
> +    trace_qauthz_is_allowed(authz, identity, allowed);
> +
> +    return allowed;
> +}
> +
> +
> +bool qauthz_is_allowed_by_id(const char *authzid,
> +                             const char *identity,
> +                             Error **errp)
> +{
> +    QAuthZ *authz;
> +    Object *obj;
> +    Object *container;
> +
> +    container = object_get_objects_root();
> +    obj = object_resolve_path_component(container,
> +                                        authzid);
> +    if (!obj) {
> +        error_setg(errp, "Cannot find QAuthZ object ID %s",
> +                   authzid);
> +        return false;
> +    }
> +
> +    if (!object_dynamic_cast(obj, TYPE_QAUTHZ)) {
> +        error_setg(errp, "Object '%s' is not a QAuthZ subclass",
> +                   authzid);
> +        return false;
> +    }
> +
> +    authz = QAUTHZ(obj);
> +
> +    return qauthz_is_allowed(authz, identity, errp);
> +}
> +
> +
> +static const TypeInfo authz_info = {
> +    .parent = TYPE_OBJECT,
> +    .name = TYPE_QAUTHZ,
> +    .instance_size = sizeof(QAuthZ),
> +    .class_size = sizeof(QAuthZClass),
> +    .abstract = true,
> +};
> +
> +static void qauthz_register_types(void)
> +{
> +    type_register_static(&authz_info);
> +}
> +
> +type_init(qauthz_register_types)
> +
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 29bbcf8c25..9624734923 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1857,6 +1857,13 @@ F: io/
>  F: include/io/
>  F: tests/test-io-*
>
> +User authorization
> +M: Daniel P. Berrange <berrange@redhat.com>
> +S: Maintained
> +F: authz/
> +F: include/authz/
> +F: tests/test-authz-*
> +
>  Sockets
>  M: Daniel P. Berrange <berrange@redhat.com>
>  M: Gerd Hoffmann <kraxel@redhat.com>
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> new file mode 100644
> index 0000000000..12597c9528
> --- /dev/null
> +++ b/authz/Makefile.objs
> @@ -0,0 +1 @@
> +authz-obj-y += base.o
> diff --git a/authz/trace-events b/authz/trace-events
> new file mode 100644
> index 0000000000..481c90f511
> --- /dev/null
> +++ b/authz/trace-events
> @@ -0,0 +1,4 @@
> +# See docs/devel/tracing.txt for syntax documentation.
> +
> +# authz/base.c
> +qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p check identity=%s allowed=%d"
> --
> 2.17.2
>
>


--
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-11-07 22:23   ` Marc-André Lureau
@ 2018-11-07 22:38     ` Eric Blake
  2018-11-13 17:29     ` Daniel P. Berrangé
  1 sibling, 0 replies; 36+ messages in thread
From: Eric Blake @ 2018-11-07 22:38 UTC (permalink / raw)
  To: Marc-André Lureau, Daniel P. Berrange
  Cc: philmd, Gerd Hoffmann, QEMU, Dr. David Alan Gilbert, Markus Armbruster

On 11/7/18 4:23 PM, Marc-André Lureau wrote:

>> +##
>> +# @QAuthZListRuleListHack:
>> +#
>> +# Not exposed via QMP; hack to generate QAuthZListRuleList
>> +# for use internally by the code.
> 
> Well, this will probably end in the documentation (it's already in the
> .json, which is one source of documentation ;).
> 
> What about adding a 'gen-list' field, or a 'pragma' listing the
> structs that should have list code generated?

We've used this hack several times in the past, but indeed there might 
be nicer design approaches to consider now that we have pragma listings. 
  But I don't see that as holding up this series.

-- 
Eric Blake, Principal Software Engineer
Red Hat, Inc.           +1-919-301-3266
Virtualization:  qemu.org | libvirt.org

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation Daniel P. Berrangé
  2018-10-23 11:14   ` Philippe Mathieu-Daudé
@ 2018-11-08  8:15   ` Marc-André Lureau
  2018-11-14 16:45     ` Daniel P. Berrangé
  1 sibling, 1 reply; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-08  8:15 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

Hi

On Fri, Oct 19, 2018 at 5:51 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> The 'qemu_acl' type was a previous non-QOM based attempt to provide an
> authorization facility in QEMU. Because it is non-QOM based it cannot be
> created via the command line and requires special monitor commands to
> manipulate it.
>
> The new QAuthZ subclasses provide a superset of the functionality in
> qemu_acl, so the latter can now be deleted. The HMP 'acl_*' monitor
> commands are converted to use the new QAuthZSimple data type instead
> in order to provide temporary backwards compatibility.
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  include/qemu/acl.h             |  66 ------------
>  ui/vnc-auth-sasl.h             |   5 +-
>  ui/vnc.h                       |   4 +-
>  crypto/tlssession.c            |  35 +++----
>  monitor.c                      | 185 ++++++++++++++++++++++-----------
>  tests/test-crypto-tlssession.c |  15 ++-
>  tests/test-io-channel-tls.c    |  16 ++-
>  ui/vnc-auth-sasl.c             |  23 ++--
>  ui/vnc-auth-vencrypt.c         |   2 +-
>  ui/vnc-ws.c                    |   2 +-
>  ui/vnc.c                       |  37 ++++---
>  util/acl.c                     | 179 -------------------------------
>  crypto/trace-events            |   2 +-
>  tests/Makefile.include         |   4 +-
>  util/Makefile.objs             |   1 -
>  15 files changed, 215 insertions(+), 361 deletions(-)
>  delete mode 100644 include/qemu/acl.h
>  delete mode 100644 util/acl.c
>
> diff --git a/include/qemu/acl.h b/include/qemu/acl.h
> deleted file mode 100644
> index 7c44119a47..0000000000
> --- a/include/qemu/acl.h
> +++ /dev/null
> @@ -1,66 +0,0 @@
> -/*
> - * QEMU access control list management
> - *
> - * Copyright (C) 2009 Red Hat, Inc
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -#ifndef QEMU_ACL_H
> -#define QEMU_ACL_H
> -
> -#include "qemu/queue.h"
> -
> -typedef struct qemu_acl_entry qemu_acl_entry;
> -typedef struct qemu_acl qemu_acl;
> -
> -struct qemu_acl_entry {
> -    char *match;
> -    int deny;
> -
> -    QTAILQ_ENTRY(qemu_acl_entry) next;
> -};
> -
> -struct qemu_acl {
> -    char *aclname;
> -    unsigned int nentries;
> -    QTAILQ_HEAD(,qemu_acl_entry) entries;
> -    int defaultDeny;
> -};
> -
> -qemu_acl *qemu_acl_init(const char *aclname);
> -
> -qemu_acl *qemu_acl_find(const char *aclname);
> -
> -int qemu_acl_party_is_allowed(qemu_acl *acl,
> -                             const char *party);
> -
> -void qemu_acl_reset(qemu_acl *acl);
> -
> -int qemu_acl_append(qemu_acl *acl,
> -                   int deny,
> -                   const char *match);
> -int qemu_acl_insert(qemu_acl *acl,
> -                   int deny,
> -                   const char *match,
> -                   int index);
> -int qemu_acl_remove(qemu_acl *acl,
> -                   const char *match);
> -
> -#endif /* QEMU_ACL_H */
> diff --git a/ui/vnc-auth-sasl.h b/ui/vnc-auth-sasl.h
> index 2ae224ee3a..fb55fe04ca 100644
> --- a/ui/vnc-auth-sasl.h
> +++ b/ui/vnc-auth-sasl.h
> @@ -30,8 +30,8 @@
>  typedef struct VncStateSASL VncStateSASL;
>  typedef struct VncDisplaySASL VncDisplaySASL;
>
> -#include "qemu/acl.h"
>  #include "qemu/main-loop.h"
> +#include "authz/base.h"
>
>  struct VncStateSASL {
>      sasl_conn_t *conn;
> @@ -60,7 +60,8 @@ struct VncStateSASL {
>  };
>
>  struct VncDisplaySASL {
> -    qemu_acl *acl;
> +    QAuthZ *authz;
> +    char *authzid;
>  };
>
>  void vnc_sasl_client_cleanup(VncState *vs);
> diff --git a/ui/vnc.h b/ui/vnc.h
> index a86e0610e8..29ee1738a5 100644
> --- a/ui/vnc.h
> +++ b/ui/vnc.h
> @@ -39,6 +39,7 @@
>  #include "io/channel-socket.h"
>  #include "io/channel-tls.h"
>  #include "io/net-listener.h"
> +#include "authz/base.h"
>  #include <zlib.h>
>
>  #include "keymaps.h"
> @@ -177,7 +178,8 @@ struct VncDisplay
>      bool lossy;
>      bool non_adaptive;
>      QCryptoTLSCreds *tlscreds;
> -    char *tlsaclname;
> +    QAuthZ *tlsauthz;
> +    char *tlsauthzid;
>  #ifdef CONFIG_VNC_SASL
>      VncDisplaySASL sasl;
>  #endif
> diff --git a/crypto/tlssession.c b/crypto/tlssession.c
> index 66a6fbe19c..23842aa95d 100644
> --- a/crypto/tlssession.c
> +++ b/crypto/tlssession.c
> @@ -24,7 +24,7 @@
>  #include "crypto/tlscredspsk.h"
>  #include "crypto/tlscredsx509.h"
>  #include "qapi/error.h"
> -#include "qemu/acl.h"
> +#include "authz/base.h"
>  #include "trace.h"
>
>  #ifdef CONFIG_GNUTLS
> @@ -37,7 +37,7 @@ struct QCryptoTLSSession {
>      QCryptoTLSCreds *creds;
>      gnutls_session_t handle;
>      char *hostname;
> -    char *aclname;
> +    char *authzid;
>      bool handshakeComplete;
>      QCryptoTLSSessionWriteFunc writeFunc;
>      QCryptoTLSSessionReadFunc readFunc;
> @@ -56,7 +56,7 @@ qcrypto_tls_session_free(QCryptoTLSSession *session)
>      gnutls_deinit(session->handle);
>      g_free(session->hostname);
>      g_free(session->peername);
> -    g_free(session->aclname);
> +    g_free(session->authzid);
>      object_unref(OBJECT(session->creds));
>      g_free(session);
>  }
> @@ -101,7 +101,7 @@ qcrypto_tls_session_pull(void *opaque, void *buf, size_t len)
>  QCryptoTLSSession *
>  qcrypto_tls_session_new(QCryptoTLSCreds *creds,
>                          const char *hostname,
> -                        const char *aclname,
> +                        const char *authzid,
>                          QCryptoTLSCredsEndpoint endpoint,
>                          Error **errp)
>  {
> @@ -111,13 +111,13 @@ qcrypto_tls_session_new(QCryptoTLSCreds *creds,
>      session = g_new0(QCryptoTLSSession, 1);
>      trace_qcrypto_tls_session_new(
>          session, creds, hostname ? hostname : "<none>",
> -        aclname ? aclname : "<none>", endpoint);
> +        authzid ? authzid : "<none>", endpoint);
>
>      if (hostname) {
>          session->hostname = g_strdup(hostname);
>      }
> -    if (aclname) {
> -        session->aclname = g_strdup(aclname);
> +    if (authzid) {
> +        session->authzid = g_strdup(authzid);
>      }
>      session->creds = creds;
>      object_ref(OBJECT(creds));
> @@ -268,6 +268,7 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
>      unsigned int nCerts, i;
>      time_t now;
>      gnutls_x509_crt_t cert = NULL;
> +    Error *err = NULL;
>
>      now = time(NULL);
>      if (now == ((time_t)-1)) {
> @@ -355,19 +356,17 @@ qcrypto_tls_session_check_certificate(QCryptoTLSSession *session,
>                             gnutls_strerror(ret));
>                  goto error;
>              }
> -            if (session->aclname) {
> -                qemu_acl *acl = qemu_acl_find(session->aclname);
> -                int allow;
> -                if (!acl) {
> -                    error_setg(errp, "Cannot find ACL %s",
> -                               session->aclname);
> +            if (session->authzid) {
> +                bool allow;
> +
> +                allow = qauthz_is_allowed_by_id(session->authzid,
> +                                                session->peername, &err);
> +                if (err) {
> +                    error_propagate(errp, err);
>                      goto error;
>                  }
> -
> -                allow = qemu_acl_party_is_allowed(acl, session->peername);
> -
>                  if (!allow) {
> -                    error_setg(errp, "TLS x509 ACL check for %s is denied",
> +                    error_setg(errp, "TLS x509 authz check for %s is denied",
>                                 session->peername);
>                      goto error;
>                  }
> @@ -558,7 +557,7 @@ qcrypto_tls_session_get_peer_name(QCryptoTLSSession *session)
>  QCryptoTLSSession *
>  qcrypto_tls_session_new(QCryptoTLSCreds *creds G_GNUC_UNUSED,
>                          const char *hostname G_GNUC_UNUSED,
> -                        const char *aclname G_GNUC_UNUSED,
> +                        const char *authzid G_GNUC_UNUSED,
>                          QCryptoTLSCredsEndpoint endpoint G_GNUC_UNUSED,
>                          Error **errp)
>  {
> diff --git a/monitor.c b/monitor.c
> index b9258a7438..2458322eb1 100644
> --- a/monitor.c
> +++ b/monitor.c
> @@ -51,7 +51,8 @@
>  #include "sysemu/balloon.h"
>  #include "qemu/timer.h"
>  #include "sysemu/hw_accel.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
> +#include "qapi/util.h"
>  #include "sysemu/tpm.h"
>  #include "qapi/qmp/qdict.h"
>  #include "qapi/qmp/qerror.h"
> @@ -2040,93 +2041,154 @@ static void hmp_wavcapture(Monitor *mon, const QDict *qdict)
>      QLIST_INSERT_HEAD (&capture_head, s, entries);
>  }
>
> -static qemu_acl *find_acl(Monitor *mon, const char *name)
> +static QAuthZList *find_auth(Monitor *mon, const char *name)
>  {
> -    qemu_acl *acl = qemu_acl_find(name);
> +    Object *obj;
> +    Object *container;
>
> -    if (!acl) {
> +    container = object_get_objects_root();
> +    obj = object_resolve_path_component(container, name);
> +    if (!obj) {
>          monitor_printf(mon, "acl: unknown list '%s'\n", name);
> +        return NULL;
>      }
> -    return acl;
> +
> +    return QAUTHZ_LIST(obj);
>  }
>
>  static void hmp_acl_show(Monitor *mon, const QDict *qdict)
>  {
>      const char *aclname = qdict_get_str(qdict, "aclname");
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    qemu_acl_entry *entry;
> -    int i = 0;
> -
> -    if (acl) {
> -        monitor_printf(mon, "policy: %s\n",
> -                       acl->defaultDeny ? "deny" : "allow");
> -        QTAILQ_FOREACH(entry, &acl->entries, next) {
> -            i++;
> -            monitor_printf(mon, "%d: %s %s\n", i,
> -                           entry->deny ? "deny" : "allow", entry->match);
> -        }
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    QAuthZListRuleList *rules;
> +    size_t i = 0;
> +
> +    if (!auth) {
> +        return;
> +    }
> +
> +    monitor_printf(mon, "policy: %s\n",
> +                   QAuthZListPolicy_lookup.array[auth->policy]);

please use QAuthZListPolicy_str()

> +
> +    rules = auth->rules;
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        i++;
> +        monitor_printf(mon, "%zu: %s %s\n", i,
> +                       QAuthZListPolicy_lookup.array[rule->policy],

QAuthZListPolicy_str

> +                       rule->match);
> +        rules = rules->next;
>      }
>  }
>
>  static void hmp_acl_reset(Monitor *mon, const QDict *qdict)
>  {
>      const char *aclname = qdict_get_str(qdict, "aclname");
> -    qemu_acl *acl = find_acl(mon, aclname);
> +    QAuthZList *auth = find_auth(mon, aclname);
>
> -    if (acl) {
> -        qemu_acl_reset(acl);
> -        monitor_printf(mon, "acl: removed all rules\n");
> +    if (!auth) {
> +        return;
>      }
> +
> +    auth->policy = QAUTHZ_LIST_POLICY_DENY;
> +    qapi_free_QAuthZListRuleList(auth->rules);
> +    auth->rules = NULL;
> +    monitor_printf(mon, "acl: removed all rules\n");
>  }
>
>  static void hmp_acl_policy(Monitor *mon, const QDict *qdict)
>  {
>      const char *aclname = qdict_get_str(qdict, "aclname");
>      const char *policy = qdict_get_str(qdict, "policy");
> -    qemu_acl *acl = find_acl(mon, aclname);
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    int val;
> +    Error *err = NULL;
> +
> +    if (!auth) {
> +        return;
> +    }
>
> -    if (acl) {
> -        if (strcmp(policy, "allow") == 0) {
> -            acl->defaultDeny = 0;
> +    val = qapi_enum_parse(&QAuthZListPolicy_lookup,
> +                          policy,
> +                          QAUTHZ_LIST_POLICY_DENY,
> +                          &err);
> +    if (err) {
> +        error_free(err);
> +        monitor_printf(mon, "acl: unknown policy '%s', "
> +                       "expected 'deny' or 'allow'\n", policy);
> +    } else {
> +        auth->policy = val;
> +        if (auth->policy == QAUTHZ_LIST_POLICY_ALLOW) {
>              monitor_printf(mon, "acl: policy set to 'allow'\n");
> -        } else if (strcmp(policy, "deny") == 0) {
> -            acl->defaultDeny = 1;
> -            monitor_printf(mon, "acl: policy set to 'deny'\n");
>          } else {
> -            monitor_printf(mon, "acl: unknown policy '%s', "
> -                           "expected 'deny' or 'allow'\n", policy);
> +            monitor_printf(mon, "acl: policy set to 'deny'\n");
>          }
>      }
>  }
>
> +static QAuthZListFormat hmp_acl_get_format(const char *match)
> +{
> +#ifdef CONFIG_FNMATCH
> +    if (strchr(match, '*')) {
> +        return QAUTHZ_LIST_FORMAT_GLOB;
> +    } else {
> +        return QAUTHZ_LIST_FORMAT_EXACT;
> +    }
> +#else
> +    /* Historically we silently degraded to plain strcmp
> +     * when fnmatch() was missing */
> +    return QAUTHZ_LIST_FORMAT_EXACT;
> +#endif
> +}
> +
>  static void hmp_acl_add(Monitor *mon, const QDict *qdict)
>  {
>      const char *aclname = qdict_get_str(qdict, "aclname");
>      const char *match = qdict_get_str(qdict, "match");
> -    const char *policy = qdict_get_str(qdict, "policy");
> +    const char *policystr = qdict_get_str(qdict, "policy");
>      int has_index = qdict_haskey(qdict, "index");
>      int index = qdict_get_try_int(qdict, "index", -1);
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    int deny, ret;
> -
> -    if (acl) {
> -        if (strcmp(policy, "allow") == 0) {
> -            deny = 0;
> -        } else if (strcmp(policy, "deny") == 0) {
> -            deny = 1;
> -        } else {
> -            monitor_printf(mon, "acl: unknown policy '%s', "
> -                           "expected 'deny' or 'allow'\n", policy);
> -            return;
> -        }
> -        if (has_index)
> -            ret = qemu_acl_insert(acl, deny, match, index);
> -        else
> -            ret = qemu_acl_append(acl, deny, match);
> -        if (ret < 0)
> -            monitor_printf(mon, "acl: unable to add acl entry\n");
> -        else
> -            monitor_printf(mon, "acl: added rule at position %d\n", ret);
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    Error *err = NULL;
> +    QAuthZListPolicy policy;
> +    QAuthZListFormat format;
> +    size_t i = 0;
> +
> +    if (!auth) {
> +        return;
> +    }
> +
> +    policy = qapi_enum_parse(&QAuthZListPolicy_lookup,
> +                             policystr,
> +                             QAUTHZ_LIST_POLICY_DENY,
> +                             &err);
> +    if (err) {
> +        error_free(err);
> +        monitor_printf(mon, "acl: unknown policy '%s', "
> +                       "expected 'deny' or 'allow'\n", policystr);
> +        return;
> +    }
> +
> +    format = hmp_acl_get_format(match);
> +
> +    if (has_index && index == 0) {
> +        monitor_printf(mon, "acl: unable to add acl entry\n");
> +        return;
> +    }
> +
> +    if (has_index) {
> +        i = qauthz_list_insert_rule(auth, match, policy,
> +                                    format, index - 1, &err);
> +    } else {
> +        i = qauthz_list_append_rule(auth, match, policy,
> +                                    format, &err);
> +    }
> +    if (err) {
> +        monitor_printf(mon, "acl: unable to add rule: %s",
> +                       error_get_pretty(err));
> +        error_free(err);
> +    } else {
> +        monitor_printf(mon, "acl: added rule at position %zu\n", i + 1);
>      }
>  }
>
> @@ -2134,15 +2196,18 @@ static void hmp_acl_remove(Monitor *mon, const QDict *qdict)
>  {
>      const char *aclname = qdict_get_str(qdict, "aclname");
>      const char *match = qdict_get_str(qdict, "match");
> -    qemu_acl *acl = find_acl(mon, aclname);
> -    int ret;
> +    QAuthZList *auth = find_auth(mon, aclname);
> +    ssize_t i = 0;
>
> -    if (acl) {
> -        ret = qemu_acl_remove(acl, match);
> -        if (ret < 0)
> -            monitor_printf(mon, "acl: no matching acl entry\n");
> -        else
> -            monitor_printf(mon, "acl: removed rule at position %d\n", ret);
> +    if (!auth) {
> +        return;
> +    }
> +
> +    i = qauthz_list_delete_rule(auth, match);
> +    if (i >= 0) {
> +        monitor_printf(mon, "acl: removed rule at position %zu\n", i + 1);
> +    } else {
> +        monitor_printf(mon, "acl: no matching acl entry\n");
>      }
>  }
>
> diff --git a/tests/test-crypto-tlssession.c b/tests/test-crypto-tlssession.c
> index 6fa9950afb..15212ec276 100644
> --- a/tests/test-crypto-tlssession.c
> +++ b/tests/test-crypto-tlssession.c
> @@ -28,7 +28,7 @@
>  #include "qom/object_interfaces.h"
>  #include "qapi/error.h"
>  #include "qemu/sockets.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
>
>  #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
>
> @@ -229,7 +229,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
>      QCryptoTLSCreds *serverCreds;
>      QCryptoTLSSession *clientSess = NULL;
>      QCryptoTLSSession *serverSess = NULL;
> -    qemu_acl *acl;
> +    QAuthZList *auth;
>      const char * const *wildcards;
>      int channel[2];
>      bool clientShake = false;
> @@ -285,11 +285,15 @@ static void test_crypto_tls_session_x509(const void *opaque)
>          SERVER_CERT_DIR);
>      g_assert(serverCreds != NULL);
>
> -    acl = qemu_acl_init("tlssessionacl");
> -    qemu_acl_reset(acl);
> +    auth = qauthz_list_new("tlssessionacl",
> +                           QAUTHZ_LIST_POLICY_DENY,
> +                           &error_abort);
>      wildcards = data->wildcards;
>      while (wildcards && *wildcards) {
> -        qemu_acl_append(acl, 0, *wildcards);
> +        qauthz_list_append_rule(auth, *wildcards,
> +                                QAUTHZ_LIST_POLICY_ALLOW,
> +                                QAUTHZ_LIST_FORMAT_GLOB,
> +                                &error_abort);
>          wildcards++;
>      }
>
> @@ -377,6 +381,7 @@ static void test_crypto_tls_session_x509(const void *opaque)
>
>      object_unparent(OBJECT(serverCreds));
>      object_unparent(OBJECT(clientCreds));
> +    object_unparent(OBJECT(auth));
>
>      qcrypto_tls_session_free(serverSess);
>      qcrypto_tls_session_free(clientSess);
> diff --git a/tests/test-io-channel-tls.c b/tests/test-io-channel-tls.c
> index 4900c6d433..43b707eba7 100644
> --- a/tests/test-io-channel-tls.c
> +++ b/tests/test-io-channel-tls.c
> @@ -29,8 +29,8 @@
>  #include "io-channel-helpers.h"
>  #include "crypto/init.h"
>  #include "crypto/tlscredsx509.h"
> -#include "qemu/acl.h"
>  #include "qapi/error.h"
> +#include "authz/list.h"
>  #include "qom/object_interfaces.h"
>
>  #ifdef QCRYPTO_HAVE_TLS_TEST_SUPPORT
> @@ -113,7 +113,7 @@ static void test_io_channel_tls(const void *opaque)
>      QIOChannelTLS *serverChanTLS;
>      QIOChannelSocket *clientChanSock;
>      QIOChannelSocket *serverChanSock;
> -    qemu_acl *acl;
> +    QAuthZList *auth;
>      const char * const *wildcards;
>      int channel[2];
>      struct QIOChannelTLSHandshakeData clientHandshake = { false, false };
> @@ -161,11 +161,15 @@ static void test_io_channel_tls(const void *opaque)
>          SERVER_CERT_DIR);
>      g_assert(serverCreds != NULL);
>
> -    acl = qemu_acl_init("channeltlsacl");
> -    qemu_acl_reset(acl);
> +    auth = qauthz_list_new("channeltlsacl",
> +                           QAUTHZ_LIST_POLICY_DENY,
> +                           &error_abort);
>      wildcards = data->wildcards;
>      while (wildcards && *wildcards) {
> -        qemu_acl_append(acl, 0, *wildcards);
> +        qauthz_list_append_rule(auth, *wildcards,
> +                                QAUTHZ_LIST_POLICY_ALLOW,
> +                                QAUTHZ_LIST_FORMAT_GLOB,
> +                                &error_abort);
>          wildcards++;
>      }
>
> @@ -253,6 +257,8 @@ static void test_io_channel_tls(const void *opaque)
>      object_unref(OBJECT(serverChanSock));
>      object_unref(OBJECT(clientChanSock));
>
> +    object_unparent(OBJECT(auth));
> +
>      close(channel[0]);
>      close(channel[1]);
>  }
> diff --git a/ui/vnc-auth-sasl.c b/ui/vnc-auth-sasl.c
> index 3751a777a4..7b2b09f242 100644
> --- a/ui/vnc-auth-sasl.c
> +++ b/ui/vnc-auth-sasl.c
> @@ -24,6 +24,7 @@
>
>  #include "qemu/osdep.h"
>  #include "qapi/error.h"
> +#include "authz/base.h"
>  #include "vnc.h"
>  #include "trace.h"
>
> @@ -146,13 +147,14 @@ size_t vnc_client_read_sasl(VncState *vs)
>  static int vnc_auth_sasl_check_access(VncState *vs)
>  {
>      const void *val;
> -    int err;
> -    int allow;
> +    int rv;
> +    Error *err = NULL;
> +    bool allow;
>
> -    err = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
> -    if (err != SASL_OK) {
> +    rv = sasl_getprop(vs->sasl.conn, SASL_USERNAME, &val);
> +    if (rv != SASL_OK) {
>          trace_vnc_auth_fail(vs, vs->auth, "Cannot fetch SASL username",
> -                            sasl_errstring(err, NULL, NULL));
> +                            sasl_errstring(rv, NULL, NULL));
>          return -1;
>      }
>      if (val == NULL) {
> @@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
>      vs->sasl.username = g_strdup((const char*)val);
>      trace_vnc_auth_sasl_username(vs, vs->sasl.username);
>
> -    if (vs->vd->sasl.acl == NULL) {
> +    if (vs->vd->sasl.authzid == NULL) {
>          trace_vnc_auth_sasl_acl(vs, 1);
>          return 0;
>      }
>
> -    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
> +    allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
> +                                    vs->sasl.username, &err);

Why not use qauthz_is_allowed() with .authz ?

> +    if (err) {
> +        trace_vnc_auth_fail(vs, vs->auth, "Error from authz",
> +                            error_get_pretty(err));
> +        error_free(err);
> +        return -1;
> +    }
>
>      trace_vnc_auth_sasl_acl(vs, allow);
>      return allow ? 0 : -1;
> diff --git a/ui/vnc-auth-vencrypt.c b/ui/vnc-auth-vencrypt.c
> index d99ea362c1..f072e16ace 100644
> --- a/ui/vnc-auth-vencrypt.c
> +++ b/ui/vnc-auth-vencrypt.c
> @@ -109,7 +109,7 @@ static int protocol_client_vencrypt_auth(VncState *vs, uint8_t *data, size_t len
>          tls = qio_channel_tls_new_server(
>              vs->ioc,
>              vs->vd->tlscreds,
> -            vs->vd->tlsaclname,
> +            vs->vd->tlsauthzid,
>              &err);
>          if (!tls) {
>              trace_vnc_auth_fail(vs, vs->auth, "TLS setup failed",
> diff --git a/ui/vnc-ws.c b/ui/vnc-ws.c
> index 950f1cd2ac..95c9703c72 100644
> --- a/ui/vnc-ws.c
> +++ b/ui/vnc-ws.c
> @@ -62,7 +62,7 @@ gboolean vncws_tls_handshake_io(QIOChannel *ioc G_GNUC_UNUSED,
>      tls = qio_channel_tls_new_server(
>          vs->ioc,
>          vs->vd->tlscreds,
> -        vs->vd->tlsaclname,
> +        vs->vd->tlsauthzid,
>          &err);
>      if (!tls) {
>          VNC_DEBUG("Failed to setup TLS %s\n", error_get_pretty(err));
> diff --git a/ui/vnc.c b/ui/vnc.c
> index cf221c83cc..60cb7c2d3d 100644
> --- a/ui/vnc.c
> +++ b/ui/vnc.c
> @@ -33,7 +33,7 @@
>  #include "qemu/option.h"
>  #include "qemu/sockets.h"
>  #include "qemu/timer.h"
> -#include "qemu/acl.h"
> +#include "authz/list.h"
>  #include "qemu/config-file.h"
>  #include "qapi/qapi-events.h"
>  #include "qapi/error.h"
> @@ -3267,12 +3267,24 @@ static void vnc_display_close(VncDisplay *vd)
>          object_unparent(OBJECT(vd->tlscreds));
>          vd->tlscreds = NULL;
>      }
> -    g_free(vd->tlsaclname);
> -    vd->tlsaclname = NULL;
> +    if (vd->tlsauthz) {
> +        object_unparent(OBJECT(vd->tlsauthz));
> +        vd->tlsauthz = NULL;
> +    }
> +    g_free(vd->tlsauthzid);
> +    vd->tlsauthzid = NULL;
>      if (vd->lock_key_sync) {
>          qemu_remove_led_event_handler(vd->led);
>          vd->led = NULL;
>      }
> +#ifdef CONFIG_VNC_SASL
> +    if (vd->sasl.authz) {
> +        object_unparent(OBJECT(vd->sasl.authz));
> +        vd->sasl.authz = NULL;
> +    }
> +    g_free(vd->sasl.authzid);
> +    vd->sasl.authzid = NULL;
> +#endif
>  }
>
>  int vnc_display_password(const char *id, const char *password)
> @@ -3925,23 +3937,24 @@ void vnc_display_open(const char *id, Error **errp)
>
>      if (acl) {
>          if (strcmp(vd->id, "default") == 0) {
> -            vd->tlsaclname = g_strdup("vnc.x509dname");
> +            vd->tlsauthzid = g_strdup("vnc.x509dname");
>          } else {
> -            vd->tlsaclname = g_strdup_printf("vnc.%s.x509dname", vd->id);
> +            vd->tlsauthzid = g_strdup_printf("vnc.%s.x509dname", vd->id);
>          }
> -        qemu_acl_init(vd->tlsaclname);
> +        vd->tlsauthz = QAUTHZ(qauthz_list_new(vd->tlsauthzid,
> +                                              QAUTHZ_LIST_POLICY_DENY,
> +                                              &error_abort));
>      }
>  #ifdef CONFIG_VNC_SASL
>      if (acl && sasl) {
> -        char *aclname;
> -
>          if (strcmp(vd->id, "default") == 0) {
> -            aclname = g_strdup("vnc.username");
> +            vd->sasl.authzid = g_strdup("vnc.username");
>          } else {
> -            aclname = g_strdup_printf("vnc.%s.username", vd->id);
> +            vd->sasl.authzid = g_strdup_printf("vnc.%s.username", vd->id);
>          }
> -        vd->sasl.acl = qemu_acl_init(aclname);
> -        g_free(aclname);
> +        vd->sasl.authz = QAUTHZ(qauthz_list_new(vd->sasl.authzid,
> +                                                QAUTHZ_LIST_POLICY_DENY,
> +                                                &error_abort));
>      }
>  #endif
>
> diff --git a/util/acl.c b/util/acl.c
> deleted file mode 100644
> index c105addadc..0000000000
> --- a/util/acl.c
> +++ /dev/null
> @@ -1,179 +0,0 @@
> -/*
> - * QEMU access control list management
> - *
> - * Copyright (C) 2009 Red Hat, Inc
> - *
> - * Permission is hereby granted, free of charge, to any person obtaining a copy
> - * of this software and associated documentation files (the "Software"), to deal
> - * in the Software without restriction, including without limitation the rights
> - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
> - * copies of the Software, and to permit persons to whom the Software is
> - * furnished to do so, subject to the following conditions:
> - *
> - * The above copyright notice and this permission notice shall be included in
> - * all copies or substantial portions of the Software.
> - *
> - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
> - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
> - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
> - * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
> - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
> - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
> - * THE SOFTWARE.
> - */
> -
> -
> -#include "qemu/osdep.h"
> -#include "qemu-common.h"
> -#include "qemu/acl.h"
> -
> -#ifdef CONFIG_FNMATCH
> -#include <fnmatch.h>
> -#endif
> -
> -
> -static unsigned int nacls = 0;
> -static qemu_acl **acls = NULL;
> -
> -
> -
> -qemu_acl *qemu_acl_find(const char *aclname)
> -{
> -    int i;
> -    for (i = 0 ; i < nacls ; i++) {
> -        if (strcmp(acls[i]->aclname, aclname) == 0)
> -            return acls[i];
> -    }
> -
> -    return NULL;
> -}
> -
> -qemu_acl *qemu_acl_init(const char *aclname)
> -{
> -    qemu_acl *acl;
> -
> -    acl = qemu_acl_find(aclname);
> -    if (acl)
> -        return acl;
> -
> -    acl = g_malloc(sizeof(*acl));
> -    acl->aclname = g_strdup(aclname);
> -    /* Deny by default, so there is no window of "open
> -     * access" between QEMU starting, and the user setting
> -     * up ACLs in the monitor */
> -    acl->defaultDeny = 1;
> -
> -    acl->nentries = 0;
> -    QTAILQ_INIT(&acl->entries);
> -
> -    acls = g_realloc(acls, sizeof(*acls) * (nacls +1));
> -    acls[nacls] = acl;
> -    nacls++;
> -
> -    return acl;
> -}
> -
> -int qemu_acl_party_is_allowed(qemu_acl *acl,
> -                              const char *party)
> -{
> -    qemu_acl_entry *entry;
> -
> -    QTAILQ_FOREACH(entry, &acl->entries, next) {
> -#ifdef CONFIG_FNMATCH
> -        if (fnmatch(entry->match, party, 0) == 0)
> -            return entry->deny ? 0 : 1;
> -#else
> -        /* No fnmatch, so fallback to exact string matching
> -         * instead of allowing wildcards */
> -        if (strcmp(entry->match, party) == 0)
> -            return entry->deny ? 0 : 1;
> -#endif
> -    }
> -
> -    return acl->defaultDeny ? 0 : 1;
> -}
> -
> -
> -void qemu_acl_reset(qemu_acl *acl)
> -{
> -    qemu_acl_entry *entry, *next_entry;
> -
> -    /* Put back to deny by default, so there is no window
> -     * of "open access" while the user re-initializes the
> -     * access control list */
> -    acl->defaultDeny = 1;
> -    QTAILQ_FOREACH_SAFE(entry, &acl->entries, next, next_entry) {
> -        QTAILQ_REMOVE(&acl->entries, entry, next);
> -        g_free(entry->match);
> -        g_free(entry);
> -    }
> -    acl->nentries = 0;
> -}
> -
> -
> -int qemu_acl_append(qemu_acl *acl,
> -                    int deny,
> -                    const char *match)
> -{
> -    qemu_acl_entry *entry;
> -
> -    entry = g_malloc(sizeof(*entry));
> -    entry->match = g_strdup(match);
> -    entry->deny = deny;
> -
> -    QTAILQ_INSERT_TAIL(&acl->entries, entry, next);
> -    acl->nentries++;
> -
> -    return acl->nentries;
> -}
> -
> -
> -int qemu_acl_insert(qemu_acl *acl,
> -                    int deny,
> -                    const char *match,
> -                    int index)
> -{
> -    qemu_acl_entry *tmp;
> -    int i = 0;
> -
> -    if (index <= 0)
> -        return -1;
> -    if (index > acl->nentries) {
> -        return qemu_acl_append(acl, deny, match);
> -    }
> -
> -    QTAILQ_FOREACH(tmp, &acl->entries, next) {
> -        i++;
> -        if (i == index) {
> -            qemu_acl_entry *entry;
> -            entry = g_malloc(sizeof(*entry));
> -            entry->match = g_strdup(match);
> -            entry->deny = deny;
> -
> -            QTAILQ_INSERT_BEFORE(tmp, entry, next);
> -            acl->nentries++;
> -            break;
> -        }
> -    }
> -
> -    return i;
> -}
> -
> -int qemu_acl_remove(qemu_acl *acl,
> -                    const char *match)
> -{
> -    qemu_acl_entry *entry;
> -    int i = 0;
> -
> -    QTAILQ_FOREACH(entry, &acl->entries, next) {
> -        i++;
> -        if (strcmp(entry->match, match) == 0) {
> -            QTAILQ_REMOVE(&acl->entries, entry, next);
> -            acl->nentries--;
> -            g_free(entry->match);
> -            g_free(entry);
> -            return i;
> -        }
> -    }
> -    return -1;
> -}
> diff --git a/crypto/trace-events b/crypto/trace-events
> index 597389b73c..a38ad7b787 100644
> --- a/crypto/trace-events
> +++ b/crypto/trace-events
> @@ -19,5 +19,5 @@ qcrypto_tls_creds_x509_load_cert(void *creds, int isServer, const char *file) "T
>  qcrypto_tls_creds_x509_load_cert_list(void *creds, const char *file) "TLS creds x509 load cert list creds=%p file=%s"
>
>  # crypto/tlssession.c
> -qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *aclname, int endpoint) "TLS session new session=%p creds=%p hostname=%s aclname=%s endpoint=%d"
> +qcrypto_tls_session_new(void *session, void *creds, const char *hostname, const char *authzid, int endpoint) "TLS session new session=%p creds=%p hostname=%s authzid=%s endpoint=%d"
>  qcrypto_tls_session_check_creds(void *session, const char *status) "TLS session check creds session=%p status=%s"
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index b2369c14cb..e8ceb4e5f6 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -510,8 +510,8 @@ test-qom-obj-y = $(qom-obj-y) $(test-util-obj-y)
>  test-qapi-obj-y = tests/test-qapi-visit.o tests/test-qapi-types.o \
>         tests/test-qapi-events.o tests/test-qapi-introspect.o \
>         $(test-qom-obj-y)
> -benchmark-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
> -test-crypto-obj-y = $(crypto-obj-y) $(test-qom-obj-y)
> +benchmark-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
> +test-crypto-obj-y = $(authz-obj-y) $(crypto-obj-y) $(test-qom-obj-y)
>  test-io-obj-y = $(io-obj-y) $(test-crypto-obj-y)
>  test-block-obj-y = $(block-obj-y) $(test-io-obj-y) tests/iothread.o
>
> diff --git a/util/Makefile.objs b/util/Makefile.objs
> index 4d7675d6e7..ca99e7e90d 100644
> --- a/util/Makefile.objs
> +++ b/util/Makefile.objs
> @@ -20,7 +20,6 @@ util-obj-y += envlist.o path.o module.o
>  util-obj-y += host-utils.o
>  util-obj-y += bitmap.o bitops.o hbitmap.o
>  util-obj-y += fifo8.o
> -util-obj-y += acl.o
>  util-obj-y += cacheinfo.o
>  util-obj-y += error.o qemu-error.o
>  util-obj-y += id.o
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
  2018-10-23 10:18   ` Philippe Mathieu-Daudé
  2018-11-07 22:23   ` Marc-André Lureau
@ 2018-11-08  8:18   ` Marc-André Lureau
  2 siblings, 0 replies; 36+ messages in thread
From: Marc-André Lureau @ 2018-11-08  8:18 UTC (permalink / raw)
  To: Daniel P. Berrange
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Fri, Oct 19, 2018 at 5:45 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
>
> From: "Daniel P. Berrange" <berrange@redhat.com>
>
> Add a QAuthZList object type that implements the QAuthZ interface. This
> built-in implementation maintains a trivial access control list with a
> sequence of match rules and a final default policy. This replicates the
> functionality currently provided by the qemu_acl module.
>
> To create an instance of this object via the QMP monitor, the syntax
> used would be:
>
>   {
>     "execute": "object-add",
>     "arguments": {
>       "qom-type": "authz-list",
>       "id": "authz0",
>       "parameters": {
>         "rules": [
>            { "match": "fred", "policy": "allow", "format": "exact" },
>            { "match": "bob", "policy": "allow", "format": "exact" },
>            { "match": "danb", "policy": "deny", "format": "glob" },
>            { "match": "dan*", "policy": "allow", "format": "exact" },
>         ],
>         "policy": "deny"
>       }
>     }
>   }
>
> This sets up an authorization rule that allows 'fred', 'bob' and anyone
> whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> denied.
>
> It is not currently possible to create this via -object, since there is
> no syntax supported to specify non-scalar properties for objects. This
> is likely to be addressed by later support for using JSON with -object,
> or an equivalent approach.
>
> In any case the future "authz-listfile" object can be used from the
> CLI and is likely a better choice, as it allows the ACL to be refreshed
> automatically on change.
>
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---
>  Makefile                |   7 +-
>  Makefile.objs           |   4 +
>  qapi/authz.json         |  58 ++++++++
>  qapi/qapi-schema.json   |   1 +
>  include/authz/list.h    | 106 ++++++++++++++
>  authz/list.c            | 309 ++++++++++++++++++++++++++++++++++++++++
>  tests/test-authz-list.c | 171 ++++++++++++++++++++++
>  .gitignore              |   4 +
>  MAINTAINERS             |   1 +
>  authz/Makefile.objs     |   1 +
>  authz/trace-events      |   4 +
>  tests/Makefile.include  |   4 +
>  12 files changed, 669 insertions(+), 1 deletion(-)
>  create mode 100644 qapi/authz.json
>  create mode 100644 include/authz/list.h
>  create mode 100644 authz/list.c
>  create mode 100644 tests/test-authz-list.c
>
> diff --git a/Makefile b/Makefile
> index 4b20ee2b19..da9ea40725 100644
> --- a/Makefile
> +++ b/Makefile
> @@ -600,7 +600,8 @@ qapi-modules = $(SRC_PATH)/qapi/qapi-schema.json $(SRC_PATH)/qapi/common.json \
>                 $(SRC_PATH)/qapi/tpm.json \
>                 $(SRC_PATH)/qapi/trace.json \
>                 $(SRC_PATH)/qapi/transaction.json \
> -               $(SRC_PATH)/qapi/ui.json
> +               $(SRC_PATH)/qapi/ui.json \
> +               $(SRC_PATH)/qapi/authz.json
>
>  qapi/qapi-builtin-types.c qapi/qapi-builtin-types.h \
>  qapi/qapi-types.c qapi/qapi-types.h \
> @@ -621,6 +622,7 @@ qapi/qapi-types-tpm.c qapi/qapi-types-tpm.h \
>  qapi/qapi-types-trace.c qapi/qapi-types-trace.h \
>  qapi/qapi-types-transaction.c qapi/qapi-types-transaction.h \
>  qapi/qapi-types-ui.c qapi/qapi-types-ui.h \
> +qapi/qapi-types-authz.c qapi/qapi-types-authz.h \
>  qapi/qapi-builtin-visit.c qapi/qapi-builtin-visit.h \
>  qapi/qapi-visit.c qapi/qapi-visit.h \
>  qapi/qapi-visit-block-core.c qapi/qapi-visit-block-core.h \
> @@ -640,6 +642,7 @@ qapi/qapi-visit-tpm.c qapi/qapi-visit-tpm.h \
>  qapi/qapi-visit-trace.c qapi/qapi-visit-trace.h \
>  qapi/qapi-visit-transaction.c qapi/qapi-visit-transaction.h \
>  qapi/qapi-visit-ui.c qapi/qapi-visit-ui.h \
> +qapi/qapi-visit-authz.c qapi/qapi-visit-authz.h \
>  qapi/qapi-commands.h qapi/qapi-commands.c \
>  qapi/qapi-commands-block-core.c qapi/qapi-commands-block-core.h \
>  qapi/qapi-commands-block.c qapi/qapi-commands-block.h \
> @@ -658,6 +661,7 @@ qapi/qapi-commands-tpm.c qapi/qapi-commands-tpm.h \
>  qapi/qapi-commands-trace.c qapi/qapi-commands-trace.h \
>  qapi/qapi-commands-transaction.c qapi/qapi-commands-transaction.h \
>  qapi/qapi-commands-ui.c qapi/qapi-commands-ui.h \
> +qapi/qapi-commands-authz.c qapi/qapi-commands-authz.h \
>  qapi/qapi-events.c qapi/qapi-events.h \
>  qapi/qapi-events-block-core.c qapi/qapi-events-block-core.h \
>  qapi/qapi-events-block.c qapi/qapi-events-block.h \
> @@ -676,6 +680,7 @@ qapi/qapi-events-tpm.c qapi/qapi-events-tpm.h \
>  qapi/qapi-events-trace.c qapi/qapi-events-trace.h \
>  qapi/qapi-events-transaction.c qapi/qapi-events-transaction.h \
>  qapi/qapi-events-ui.c qapi/qapi-events-ui.h \
> +qapi/qapi-events-authz.c qapi/qapi-events-authz.h \
>  qapi/qapi-introspect.h qapi/qapi-introspect.c \
>  qapi/qapi-doc.texi: \
>  qapi-gen-timestamp ;
> diff --git a/Makefile.objs b/Makefile.objs
> index ecb1071c4f..825c5863ac 100644
> --- a/Makefile.objs
> +++ b/Makefile.objs
> @@ -21,6 +21,7 @@ util-obj-y += qapi/qapi-types-tpm.o
>  util-obj-y += qapi/qapi-types-trace.o
>  util-obj-y += qapi/qapi-types-transaction.o
>  util-obj-y += qapi/qapi-types-ui.o
> +util-obj-y += qapi/qapi-types-authz.o
>  util-obj-y += qapi/qapi-builtin-visit.o
>  util-obj-y += qapi/qapi-visit.o
>  util-obj-y += qapi/qapi-visit-block-core.o
> @@ -40,6 +41,7 @@ util-obj-y += qapi/qapi-visit-tpm.o
>  util-obj-y += qapi/qapi-visit-trace.o
>  util-obj-y += qapi/qapi-visit-transaction.o
>  util-obj-y += qapi/qapi-visit-ui.o
> +util-obj-y += qapi/qapi-visit-authz.o
>  util-obj-y += qapi/qapi-events.o
>  util-obj-y += qapi/qapi-events-block-core.o
>  util-obj-y += qapi/qapi-events-block.o
> @@ -58,6 +60,7 @@ util-obj-y += qapi/qapi-events-tpm.o
>  util-obj-y += qapi/qapi-events-trace.o
>  util-obj-y += qapi/qapi-events-transaction.o
>  util-obj-y += qapi/qapi-events-ui.o
> +util-obj-y += qapi/qapi-events-authz.o
>  util-obj-y += qapi/qapi-introspect.o
>
>  chardev-obj-y = chardev/
> @@ -160,6 +163,7 @@ common-obj-y += qapi/qapi-commands-tpm.o
>  common-obj-y += qapi/qapi-commands-trace.o
>  common-obj-y += qapi/qapi-commands-transaction.o
>  common-obj-y += qapi/qapi-commands-ui.o
> +common-obj-y += qapi/qapi-commands-authz.o
>  common-obj-y += qapi/qapi-introspect.o
>  common-obj-y += qmp.o hmp.o
>  endif
> diff --git a/qapi/authz.json b/qapi/authz.json
> new file mode 100644
> index 0000000000..607839c627
> --- /dev/null
> +++ b/qapi/authz.json
> @@ -0,0 +1,58 @@
> +# -*- Mode: Python -*-
> +#
> +# QAPI authz definitions
> +
> +##
> +# @QAuthZListPolicy:
> +#
> +# The authorization policy result
> +#
> +# @deny: deny access
> +# @allow: allow access
> +#
> +# Since: 3.0
> +##
> +{ 'enum': 'QAuthZListPolicy',
> +  'prefix': 'QAUTHZ_LIST_POLICY',
> +  'data': ['deny', 'allow']}
> +
> +##
> +# @QAuthZListFormat:
> +#
> +# The authorization policy result
> +#
> +# @exact: an exact string match
> +# @glob: string with ? and * shell wildcard support
> +#
> +# Since: 3.0
> +##
> +{ 'enum': 'QAuthZListFormat',
> +  'prefix': 'QAUTHZ_LIST_FORMAT',
> +  'data': ['exact', 'glob']}
> +
> +##
> +# @QAuthZListRule:
> +#
> +# A single authorization rule.
> +#
> +# @match: a glob to match against a user identity
> +# @policy: the result to return if @match evaluates to true
> +# @format: (optional) the format of the @match rule (default 'exact')
> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRule',
> +  'data': {'match': 'str',
> +           'policy': 'QAuthZListPolicy',
> +           '*format': 'QAuthZListFormat'}}
> +
> +##
> +# @QAuthZListRuleListHack:
> +#
> +# Not exposed via QMP; hack to generate QAuthZListRuleList
> +# for use internally by the code.
> +#
> +# Since: 3.0
> +##
> +{ 'struct': 'QAuthZListRuleListHack',
> +  'data': { 'unused': ['QAuthZListRule'] } }
> diff --git a/qapi/qapi-schema.json b/qapi/qapi-schema.json
> index 65b6dc2f6f..6a5a02a388 100644
> --- a/qapi/qapi-schema.json
> +++ b/qapi/qapi-schema.json
> @@ -89,6 +89,7 @@
>  { 'include': 'rocker.json' }
>  { 'include': 'tpm.json' }
>  { 'include': 'ui.json' }
> +{ 'include': 'authz.json' }
>  { 'include': 'migration.json' }
>  { 'include': 'transaction.json' }
>  { 'include': 'trace.json' }
> diff --git a/include/authz/list.h b/include/authz/list.h
> new file mode 100644
> index 0000000000..eb131ba0f0
> --- /dev/null
> +++ b/include/authz/list.h
> @@ -0,0 +1,106 @@
> +/*
> + * QEMU list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#ifndef QAUTHZ_LIST_H__
> +#define QAUTHZ_LIST_H__
> +
> +#include "authz/base.h"
> +#include "qapi/qapi-types-authz.h"
> +
> +#define TYPE_QAUTHZ_LIST "authz-list"
> +
> +#define QAUTHZ_LIST_CLASS(klass)                        \
> +    OBJECT_CLASS_CHECK(QAuthZListClass, (klass),        \
> +                       TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST_GET_CLASS(obj)              \
> +    OBJECT_GET_CLASS(QAuthZListClass, (obj),    \
> +                      TYPE_QAUTHZ_LIST)
> +#define QAUTHZ_LIST(obj) \
> +    INTERFACE_CHECK(QAuthZList, (obj),          \
> +                    TYPE_QAUTHZ_LIST)
> +
> +typedef struct QAuthZList QAuthZList;
> +typedef struct QAuthZListClass QAuthZListClass;
> +
> +
> +/**
> + * QAuthZList:
> + *
> + * This authorization driver provides a list mechanism
> + * for granting access by matching user names against a
> + * list of globs. Each match rule has an associated policy
> + * and a catch all policy applies if no rule matches
> + *
> + * To create an instance of this class via QMP:
> + *
> + *  {
> + *    "execute": "object-add",
> + *    "arguments": {
> + *      "qom-type": "authz-list",
> + *      "id": "authz0",
> + *      "parameters": {
> + *        "rules": [
> + *           { "match": "fred", "policy": "allow", "format": "exact" },
> + *           { "match": "bob", "policy": "allow", "format": "exact" },
> + *           { "match": "danb", "policy": "deny", "format": "exact" },
> + *           { "match": "dan*", "policy": "allow", "format": "glob" }
> + *        ],
> + *        "policy": "deny"
> + *      }
> + *    }
> + *  }
> + *
> + */
> +struct QAuthZList {
> +    QAuthZ parent_obj;
> +
> +    QAuthZListPolicy policy;
> +    QAuthZListRuleList *rules;
> +};
> +
> +
> +struct QAuthZListClass {
> +    QAuthZClass parent_class;
> +};
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp);
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp);
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth,
> +                                const char *match);
> +
> +
> +#endif /* QAUTHZ_LIST_H__ */
> +
> diff --git a/authz/list.c b/authz/list.c
> new file mode 100644
> index 0000000000..1d9544681c
> --- /dev/null
> +++ b/authz/list.c
> @@ -0,0 +1,309 @@
> +/*
> + * QEMU access control list authorization driver
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +#include "authz/list.h"
> +#include "authz/trace.h"
> +#include "qom/object_interfaces.h"
> +#include "qapi/qapi-visit-authz.h"
> +
> +#ifdef CONFIG_FNMATCH
> +#include <fnmatch.h>
> +#endif
> +
> +static bool qauthz_list_is_allowed(QAuthZ *authz,
> +                                   const char *identity,
> +                                   Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(authz);
> +    QAuthZListRuleList *rules = lauthz->rules;
> +
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        QAuthZListFormat format = rule->has_format ? rule->format :
> +            QAUTHZ_LIST_FORMAT_EXACT;
> +
> +        trace_qauthz_list_check_rule(authz, rule->match, identity,
> +                                     format, rule->policy);
> +        switch (format) {
> +        case QAUTHZ_LIST_FORMAT_EXACT:
> +            if (strcmp(rule->match, identity) == 0) {
> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#ifdef CONFIG_FNMATCH
> +        case QAUTHZ_LIST_FORMAT_GLOB:
> +            if (fnmatch(rule->match, identity, 0) == 0) {
> +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +            }
> +            break;
> +#else
> +            return false;
> +#endif
> +        default:
> +            return false;
> +        }
> +        rules = rules->next;
> +    }
> +
> +    trace_qauthz_list_default_policy(authz, identity, lauthz->policy);
> +    return lauthz->policy == QAUTHZ_LIST_POLICY_ALLOW;
> +}
> +
> +
> +static void
> +qauthz_list_prop_set_policy(Object *obj,
> +                            int value,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    lauthz->policy = value;
> +}
> +
> +
> +static int
> +qauthz_list_prop_get_policy(Object *obj,
> +                            Error **errp G_GNUC_UNUSED)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    return lauthz->policy;
> +}
> +
> +
> +static void
> +qauthz_list_prop_get_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +}
> +
> +static void
> +qauthz_list_prop_set_rules(Object *obj, Visitor *v, const char *name,
> +                           void *opaque, Error **errp)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +    QAuthZListRuleList *oldrules;
> +#ifndef CONFIG_FNMATCH
> +    QAuthZListRuleList *rules;
> +#endif
> +
> +    oldrules = lauthz->rules;
> +    visit_type_QAuthZListRuleList(v, name, &lauthz->rules, errp);
> +
> +#ifndef CONFIG_FNMATCH
> +    rules = lauthz->rules;
> +    while (rules) {
> +        QAuthZListRule *rule = rules->value;
> +        if (rule->has_format &&
> +            rule->format == QAUTHZ_LIST_FORMAT_GLOB) {
> +            error_setg(errp, "Glob format not supported on this platform");
> +            qapi_free_QAuthZListRuleList(lauthz->rules);
> +            lauthz->rules = oldrules;
> +            return;
> +        }
> +    }
> +#endif
> +
> +    qapi_free_QAuthZListRuleList(oldrules);
> +}
> +
> +
> +static void
> +qauthz_list_finalize(Object *obj)
> +{
> +    QAuthZList *lauthz = QAUTHZ_LIST(obj);
> +
> +    qapi_free_QAuthZListRuleList(lauthz->rules);
> +}
> +
> +
> +static void
> +qauthz_list_class_init(ObjectClass *oc, void *data)
> +{
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    object_class_property_add_enum(oc, "policy",
> +                                   "QAuthZListPolicy",
> +                                   &QAuthZListPolicy_lookup,
> +                                   qauthz_list_prop_get_policy,
> +                                   qauthz_list_prop_set_policy,
> +                                   NULL);
> +
> +    object_class_property_add(oc, "rules", "QAuthZListRule",
> +                              qauthz_list_prop_get_rules,
> +                              qauthz_list_prop_set_rules,
> +                              NULL, NULL, NULL);
> +
> +    authz->is_allowed = qauthz_list_is_allowed;
> +}
> +
> +
> +QAuthZList *qauthz_list_new(const char *id,
> +                            QAuthZListPolicy policy,
> +                            Error **errp)
> +{
> +    return QAUTHZ_LIST(
> +        object_new_with_props(TYPE_QAUTHZ_LIST,
> +                              object_get_objects_root(),
> +                              id, errp,
> +                              "policy", QAuthZListPolicy_lookup.array[policy],

Please use QAuthZListPolicy_str

> +                              NULL));
> +}
> +
> +ssize_t qauthz_list_append_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules) {
> +        while (rules->next) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_insert_rule(QAuthZList *auth,
> +                                const char *match,
> +                                QAuthZListPolicy policy,
> +                                QAuthZListFormat format,
> +                                size_t index,
> +                                Error **errp)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +#ifndef CONFIG_FNMATCH
> +    if (format == QAUTHZ_LIST_FORMAT_GLOB) {
> +        error_setg(errp, "Glob format not supported on this platform");
> +        return -1;
> +    }
> +#endif
> +
> +    rule = g_new0(QAuthZListRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +    rule->format = format;
> +    rule->has_format = true;
> +
> +    tmp = g_new0(QAuthZListRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules && index > 0) {
> +        while (rules->next && i < (index - 1)) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        tmp->next = rules->next;
> +        rules->next = tmp;
> +        return i + 1;
> +    } else {
> +        tmp->next = auth->rules;
> +        auth->rules = tmp;
> +        return 0;
> +    }
> +}
> +
> +
> +ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
> +{
> +    QAuthZListRule *rule;
> +    QAuthZListRuleList *rules, *prev;
> +    size_t i = 0;
> +
> +    prev = NULL;
> +    rules = auth->rules;
> +    while (rules) {
> +        rule = rules->value;
> +        if (g_str_equal(rule->match, match)) {
> +            if (prev) {
> +                prev->next = rules->next;
> +            } else {
> +                auth->rules = rules->next;
> +            }
> +            rules->next = NULL;
> +            qapi_free_QAuthZListRuleList(rules);
> +            return i;
> +        }
> +        prev = rules;
> +        rules = rules->next;
> +        i++;
> +    }
> +
> +    return -1;
> +}
> +
> +
> +static const TypeInfo qauthz_list_info = {
> +    .parent = TYPE_QAUTHZ,
> +    .name = TYPE_QAUTHZ_LIST,
> +    .instance_size = sizeof(QAuthZList),
> +    .instance_finalize = qauthz_list_finalize,
> +    .class_size = sizeof(QAuthZListClass),
> +    .class_init = qauthz_list_class_init,
> +    .interfaces = (InterfaceInfo[]) {
> +        { TYPE_USER_CREATABLE },
> +        { }
> +    }
> +};
> +
> +
> +static void
> +qauthz_list_register_types(void)
> +{
> +    type_register_static(&qauthz_list_info);
> +}
> +
> +
> +type_init(qauthz_list_register_types);
> diff --git a/tests/test-authz-list.c b/tests/test-authz-list.c
> new file mode 100644
> index 0000000000..02ce4c5763
> --- /dev/null
> +++ b/tests/test-authz-list.c
> @@ -0,0 +1,171 @@
> +/*
> + * QEMU list authorization object
> + *
> + * Copyright (c) 2018 Red Hat, Inc.
> + *
> + * This library is free software; you can redistribute it and/or
> + * modify it under the terms of the GNU Lesser General Public
> + * License as published by the Free Software Foundation; either
> + * version 2 of the License, or (at your option) any later version.
> + *
> + * This library is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
> + * Lesser General Public License for more details.
> + *
> + * You should have received a copy of the GNU Lesser General Public
> + * License along with this library; if not, see <http://www.gnu.org/licenses/>.
> + *
> + */
> +
> +#include "qemu/osdep.h"
> +
> +#include "authz/list.h"
> +
> +static void test_authz_default_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_default_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_deny(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_explicit_allow(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +
> +static void test_authz_complex(void)
> +{
> +#ifndef CONFIG_FNMATCH
> +    Error *local_err = NULL;
> +#endif
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_DENY,
> +                                       &error_abort);
> +
> +    qauthz_list_append_rule(auth, "fred", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "bob", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +    qauthz_list_append_rule(auth, "dan", QAUTHZ_LIST_POLICY_DENY,
> +                            QAUTHZ_LIST_FORMAT_EXACT, &error_abort);
> +#ifdef CONFIG_FNMATCH
> +    qauthz_list_append_rule(auth, "dan*", QAUTHZ_LIST_POLICY_ALLOW,
> +                            QAUTHZ_LIST_FORMAT_GLOB, &error_abort);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "bob", &error_abort));
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "danb", &error_abort));
> +#else
> +    g_assert(qauthz_list_append_rule(auth, "dan*",
> +                                     QAUTHZ_LIST_POLICY_ALLOW,
> +                                     QAUTHZ_LIST_FORMAT_GLOB,
> +                                     &local_err) < 0);
> +    g_assert(local_err != NULL);
> +    error_free(local_err);
> +#endif
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +static void test_authz_add_remove(void)
> +{
> +    QAuthZList *auth = qauthz_list_new("auth0",
> +                                       QAUTHZ_LIST_POLICY_ALLOW,
> +                                       &error_abort);
> +
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "fred",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 0);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "bob",
> +                                            QAUTHZ_LIST_POLICY_ALLOW,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 1);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 2);
> +    g_assert_cmpint(qauthz_list_append_rule(auth, "frank",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            &error_abort),
> +                    ==, 3);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_delete_rule(auth, "dan"),
> +                    ==, 2);
> +
> +    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    g_assert_cmpint(qauthz_list_insert_rule(auth, "dan",
> +                                            QAUTHZ_LIST_POLICY_DENY,
> +                                            QAUTHZ_LIST_FORMAT_EXACT,
> +                                            2,
> +                                            &error_abort),
> +                    ==, 2);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
> +
> +    object_unparent(OBJECT(auth));
> +}
> +
> +int main(int argc, char **argv)
> +{
> +    g_test_init(&argc, &argv, NULL);
> +
> +    module_call_init(MODULE_INIT_QOM);
> +
> +    g_test_add_func("/auth/list/default/deny", test_authz_default_deny);
> +    g_test_add_func("/auth/list/default/allow", test_authz_default_allow);
> +    g_test_add_func("/auth/list/explicit/deny", test_authz_explicit_deny);
> +    g_test_add_func("/auth/list/explicit/allow", test_authz_explicit_allow);
> +    g_test_add_func("/auth/list/complex", test_authz_complex);
> +    g_test_add_func("/auth/list/add-remove", test_authz_add_remove);
> +
> +    return g_test_run();
> +}
> diff --git a/.gitignore b/.gitignore
> index 64efdfd929..9b9fcad829 100644
> --- a/.gitignore
> +++ b/.gitignore
> @@ -47,6 +47,7 @@
>  /qapi/qapi-commands-trace.[ch]
>  /qapi/qapi-commands-transaction.[ch]
>  /qapi/qapi-commands-ui.[ch]
> +/qapi/qapi-commands-authz.[ch]
>  /qapi/qapi-commands.[ch]
>  /qapi/qapi-events-block-core.[ch]
>  /qapi/qapi-events-block.[ch]
> @@ -65,6 +66,7 @@
>  /qapi/qapi-events-trace.[ch]
>  /qapi/qapi-events-transaction.[ch]
>  /qapi/qapi-events-ui.[ch]
> +/qapi/qapi-events-authz.[ch]
>  /qapi/qapi-events.[ch]
>  /qapi/qapi-introspect.[ch]
>  /qapi/qapi-types-block-core.[ch]
> @@ -84,6 +86,7 @@
>  /qapi/qapi-types-trace.[ch]
>  /qapi/qapi-types-transaction.[ch]
>  /qapi/qapi-types-ui.[ch]
> +/qapi/qapi-types-authz.[ch]
>  /qapi/qapi-types.[ch]
>  /qapi/qapi-visit-block-core.[ch]
>  /qapi/qapi-visit-block.[ch]
> @@ -102,6 +105,7 @@
>  /qapi/qapi-visit-trace.[ch]
>  /qapi/qapi-visit-transaction.[ch]
>  /qapi/qapi-visit-ui.[ch]
> +/qapi/qapi-visit-authz.[ch]
>  /qapi/qapi-visit.[ch]
>  /qapi/qapi-doc.texi
>  /qemu-doc.html
> diff --git a/MAINTAINERS b/MAINTAINERS
> index 9624734923..5d33cf4605 100644
> --- a/MAINTAINERS
> +++ b/MAINTAINERS
> @@ -1861,6 +1861,7 @@ User authorization
>  M: Daniel P. Berrange <berrange@redhat.com>
>  S: Maintained
>  F: authz/
> +F: qapi/authz.json
>  F: include/authz/
>  F: tests/test-authz-*
>
> diff --git a/authz/Makefile.objs b/authz/Makefile.objs
> index 2a75d53840..921fa624d7 100644
> --- a/authz/Makefile.objs
> +++ b/authz/Makefile.objs
> @@ -1,2 +1,3 @@
>  authz-obj-y += base.o
>  authz-obj-y += simple.o
> +authz-obj-y += list.o
> diff --git a/authz/trace-events b/authz/trace-events
> index 1ef796c1e1..a896d876e8 100644
> --- a/authz/trace-events
> +++ b/authz/trace-events
> @@ -5,3 +5,7 @@ qauthz_is_allowed(void *authz, const char *identity, bool allowed) "AuthZ %p che
>
>  # auth/simple.c
>  qauthz_simple_is_allowed(void *authz, const char *wantidentity, const char *gotidentity) "AuthZ simple %p check want identity=%s got identity=%s"
> +
> +# auth/list.c
> +qauthz_list_check_rule(void *authz, const char *identity, const char *rule, int format, int policy) "AuthZ list %p check rule=%s identity=%s format=%d policy=%d"
> +qauthz_list_default_policy(void *authz, const char *identity, int policy) "AuthZ list %p default identity=%s policy=%d"
> diff --git a/tests/Makefile.include b/tests/Makefile.include
> index 7fe8578972..b2369c14cb 100644
> --- a/tests/Makefile.include
> +++ b/tests/Makefile.include
> @@ -126,6 +126,7 @@ check-unit-y += tests/test-bufferiszero$(EXESUF)
>  check-unit-y += tests/test-uuid$(EXESUF)
>  check-unit-y += tests/ptimer-test$(EXESUF)
>  check-unit-y += tests/test-qapi-util$(EXESUF)
> +check-unit-y += tests/test-authz-list$(EXESUF)
>
>  check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
>
> @@ -572,6 +573,9 @@ tests/test-timed-average$(EXESUF): tests/test-timed-average.o $(test-util-obj-y)
>  tests/test-base64$(EXESUF): tests/test-base64.o $(test-util-obj-y)
>  tests/ptimer-test$(EXESUF): tests/ptimer-test.o tests/ptimer-test-stubs.o hw/core/ptimer.o
>
> +tests/test-authz-list$(EXESUF): tests/test-authz-list.o \
> +       $(test-util-obj-y) $(authz-obj-y) $(qom-obj-y)
> +
>  tests/test-logging$(EXESUF): tests/test-logging.o $(test-util-obj-y)
>
>  tests/test-replication$(EXESUF): tests/test-replication.o $(test-util-obj-y) \
> --
> 2.17.2
>
>


-- 
Marc-André Lureau

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner
  2018-11-07 18:08   ` Marc-André Lureau
@ 2018-11-12 16:49     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-12 16:49 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Wed, Nov 07, 2018 at 10:08:05PM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Fri, Oct 19, 2018 at 5:41 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > The inotify userspace API for reading events is quite horrible, so it is
> > useful to wrap it in a more friendly API to avoid duplicating code
> > across many users in QEMU. Wrapping it also allows introduction of a
> > platform portability layer, so that we can add impls for non-Linux based
> > equivalents in future.
> >
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---

> > +struct QFileMonitor {
> > +    QemuMutex lock;
> > +    int fd;
> > +
> > +    GHashTable *dirs; /* dirname => QFileMonitorDir */
> > +    GHashTable *idmap; /* inotify ID => dirname */
> > +};
> > +
> > +
> > +typedef struct {
> > +    int id; /* watch ID */
> > +    char *filename; /* optional filter */
> > +    QFileMonitorHandler cb;
> > +    void *opaque;
> > +} QFileMonitorWatch;
> > +
> > +
> > +typedef struct {
> > +    char *path;
> > +    int id; /* inotify ID */
> > +    int nextid; /* watch ID counter */
> > +    gsize nwatches;
> > +    QFileMonitorWatch *watches;
> > +} QFileMonitorDir;
> > +
> > +
> > +#ifdef CONFIG_INOTIFY1
> > +#include <sys/inotify.h>
> > +
> > +static void qemu_file_monitor_watch(void *arg)
> > +{
> > +    QFileMonitor *mon = arg;
> > +    char buf[4096]
> > +        __attribute__ ((aligned(__alignof__(struct inotify_event))));
> > +    int used = 0;
> > +    int len = read(mon->fd, buf, sizeof(buf));
> > +
> > +    qemu_mutex_lock(&mon->lock);
> 
> I suppose the lock should guard from mon->fd above, or there might be
> a race when modifying/removing a watch from a different thread.

The mutex is only there to protect the "dirs" and "idmap" hash tables.

The "fd" has enough safety - the kernel is fine with you reading
from the fd, while another thread is adding/removing files on
the inotify handle.

Since the QFileMonitor object is a singleton that can never be
free'd we'll never race with closing 'fd either.

> 
> > +
> > +    if (len < 0) {
> > +        if (errno != EAGAIN) {
> > +            error_report("Failure monitoring inotify FD, disabling events");
> 
> strerror(errno) could be useful

Yep.


> > +static void
> > +qemu_file_monitor_dir_free(void *data)
> > +{
> > +    QFileMonitorDir *dir = data;
> > +
> > +    g_free(dir->watches);
> 
> for sake, I would add
> assert(dir->nwatches = 0)

Yep.


> > +#ifdef CONFIG_INOTIFY1
> > +int
> > +qemu_file_monitor_add_watch(QFileMonitor *mon,
> > +                            const char *dirpath,
> > +                            const char *filename,
> > +                            QFileMonitorHandler cb,
> > +                            void *opaque,
> > +                            Error **errp)
> > +{
> > +    QFileMonitorDir *dir;
> > +    int ret = -1;
> > +
> > +    qemu_mutex_lock(&mon->lock);
> > +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> > +    if (!dir) {
> > +        int rv = inotify_add_watch(mon->fd, dirpath,
> > +                                   IN_CREATE | IN_DELETE | IN_MODIFY |
> > +                                   IN_MOVED_TO | IN_MOVED_FROM);
> > +
> > +        if (rv < 0) {
> > +            error_setg_errno(errp, errno, "Unable to watch '%s'", dirpath);
> > +            goto cleanup;
> > +        }
> > +
> > +        trace_qemu_file_monitor_enable_watch(mon, dirpath, rv);
> > +
> > +        dir = g_new0(QFileMonitorDir, 1);
> > +        dir->path = g_strdup(dirpath);
> > +        dir->id = rv;
> > +
> > +        g_hash_table_insert(mon->dirs, dir->path, dir);
> > +        g_hash_table_insert(mon->idmap, GINT_TO_POINTER(rv), dir);
> > +
> > +        if (g_hash_table_size(mon->dirs) == 1) {
> > +            qemu_set_fd_handler(mon->fd, qemu_file_monitor_watch, NULL, mon);
> > +        }
> > +    }
> > +
> > +    dir->watches = g_renew(QFileMonitorWatch, dir->watches, dir->nwatches + 1);
> 
> GArray could eventually make handling of watches a bit simpler
> (counting, resizing, removing etc)

ok, i'll have a look at that API.

> 
> > +
> > +    dir->watches[dir->nwatches].id = ++dir->nextid;
> > +    dir->watches[dir->nwatches].filename = filename ? g_strdup(filename) : NULL;
> 
> g_strdup(NULL) returns NULL already

Ah, I forget that.

> 
> > +    dir->watches[dir->nwatches].cb = cb;
> > +    dir->watches[dir->nwatches].opaque = opaque;
> > +    dir->nwatches++;
> > +
> > +    trace_qemu_file_monitor_add_watch(mon, dirpath,
> > +                                      filename ? filename : "<none>",
> > +                                      cb, opaque,
> > +                                      dir->watches[dir->nwatches - 1].id);
> > +
> > +    ret = 0;
> > +
> > + cleanup:
> > +    qemu_mutex_unlock(&mon->lock);
> > +    return ret;
> > +}
> > +
> > +
> > +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> > +                                    const char *dirpath,
> > +                                    int id)
> > +{
> > +    QFileMonitorDir *dir;
> > +    gsize i;
> > +
> > +    qemu_mutex_lock(&mon->lock);
> > +
> > +    trace_qemu_file_monitor_remove_watch(mon, dirpath, id);
> > +
> > +    dir = g_hash_table_lookup(mon->dirs, dirpath);
> > +    if (!dir) {
> > +        goto cleanup;
> > +    }
> > +
> > +    for (i = 0; i < dir->nwatches; i++) {
> > +        if (dir->watches[i].id == id) {
> > +            if (i < (dir->nwatches - 1)) {
> > +                memmove(dir->watches + i,
> > +                        dir->watches + i + 1,
> > +                        sizeof(QFileMonitorWatch) *
> > +                        (dir->nwatches - (i + 1)));
> > +                dir->watches = g_renew(QFileMonitorWatch, dir->watches,
> > +                                       dir->nwatches - 1);
> > +                dir->nwatches--;
> > +            }
> > +            break;
> > +        }
> > +    }
> > +
> > +    if (dir->nwatches == 0) {
> > +        inotify_rm_watch(mon->fd, dir->id);
> > +        trace_qemu_file_monitor_disable_watch(mon, dir->path, dir->id);
> > +
> > +        g_hash_table_remove(mon->idmap, GINT_TO_POINTER(dir->id));
> > +        g_hash_table_remove(mon->dirs, dir->path);
> > +    }
> > +
> > + cleanup:
> > +    qemu_mutex_lock(&mon->lock);
> > +}
> > +
> > +#else
> > +int
> > +qemu_file_monitor_add_watch(QFileMonitor *mon,
> > +                            const char *dirpath,
> > +                            const char *filename,
> > +                            QFileMonitorHandler cb,
> > +                            void *opaque,
> > +                            Error **errp)
> > +{
> > +    error_setg(errp, "File monitoring not available on this platform");
> > +    return -1;
> > +}
> > +
> > +void qemu_file_monitor_remove_watch(QFileMonitor *mon,
> > +                                    const char *dirpath,
> > +                                    int id)
> > +{
> > +}
> 
> Wouldn't it be cleaner with stubs/ ?

I guess we could do it that way.


> 
> Looks good, but would be even better with tests :)

I'll see what I can do about that.

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs
  2018-11-07 18:26   ` Marc-André Lureau
@ 2018-11-13 17:07     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-13 17:07 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Wed, Nov 07, 2018 at 10:26:29PM +0400, Marc-André Lureau wrote:
> On Fri, Oct 19, 2018 at 5:42 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > The internal inotify APIs allow alot of conditional statements to be
> 
> a lot
> 
> > cleared out, and provide a simpler callback for handling events.
> >
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  hw/usb/dev-mtp.c    | 250 ++++++++++++++++----------------------------
> >  hw/usb/trace-events |   2 +-
> >  2 files changed, 93 insertions(+), 159 deletions(-)
> >
> > diff --git a/hw/usb/dev-mtp.c b/hw/usb/dev-mtp.c
> > index ccbe25820b..1a8d0f088d 100644
> > --- a/hw/usb/dev-mtp.c
> > +++ b/hw/usb/dev-mtp.c
> > @@ -15,13 +15,11 @@
> >  #include <dirent.h>
> >
> >  #include <sys/statvfs.h>
> > -#ifdef CONFIG_INOTIFY1
> > -#include <sys/inotify.h>
> > -#include "qemu/main-loop.h"
> > -#endif
> > +
> >
> >  #include "qemu-common.h"
> >  #include "qemu/iov.h"
> > +#include "qemu/filemonitor.h"
> >  #include "trace.h"
> >  #include "hw/usb.h"
> >  #include "desc.h"
> > @@ -124,7 +122,6 @@ enum {
> >      EP_EVENT,
> >  };
> >
> > -#ifdef CONFIG_INOTIFY1
> >  typedef struct MTPMonEntry MTPMonEntry;
> >
> >  struct MTPMonEntry {
> > @@ -133,7 +130,6 @@ struct MTPMonEntry {
> >
> >      QTAILQ_ENTRY(MTPMonEntry) next;
> >  };
> > -#endif
> >
> >  struct MTPControl {
> >      uint16_t     code;
> > @@ -162,10 +158,8 @@ struct MTPObject {
> >      char         *name;
> >      char         *path;
> >      struct stat  stat;
> > -#ifdef CONFIG_INOTIFY1
> > -    /* inotify watch cookie */
> > +    /* file monitor watch cookie */
> >      int          watchfd;
> 
> Why not rename it watchid to avoid confusion?

Yes, will do.

> > -static void inotify_watchfn(void *arg)
> > +static void file_monitor_event(int wd,
> > +                               QFileMonitorEvent ev,
> > +                               const char *name,
> > +                               void *opaque)
> >  {
> > -    MTPState *s = arg;
> > -    ssize_t bytes;
> > -    /* From the man page: atleast one event can be read */
> > -    int pos;
> > -    char buf[sizeof(struct inotify_event) + NAME_MAX + 1];
> > -
> > -    for (;;) {
> > -        bytes = read(s->inotifyfd, buf, sizeof(buf));
> > -        pos = 0;
> > -
> > -        if (bytes <= 0) {
> > -            /* Better luck next time */
> > +    MTPState *s = opaque;
> > +    int watchfd = 0;
> > +    MTPObject *parent = usb_mtp_object_lookup_wd(s, wd);
> > +    MTPMonEntry *entry = NULL;
> > +    MTPObject *o;
> > +
> > +    if (!parent) {
> > +        return;
> > +    }
> > +
> > +    switch (ev) {
> > +    case QFILE_MONITOR_EVENT_CREATED:
> > +        if (usb_mtp_object_lookup_name(parent, name, -1)) {
> > +            /* Duplicate create event */
> >              return;
> >          }
> > +        entry = g_new0(MTPMonEntry, 1);
> > +        entry->handle = s->next_handle;
> > +        entry->event = EVT_OBJ_ADDED;
> > +        o = usb_mtp_add_child(s, parent, name);
> > +        if (!o) {
> > +            g_free(entry);
> > +            return;
> > +        }
> > +        o->watchfd = watchfd;
> 
> this effectively always set o->watchfd to 0, which is already
> initialized to 0 with g_new0(), you can drop it

Yeah, pre-existing pointless code, so I've dropped it.


> >  static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
> >  {
> >      struct dirent *entry;
> >      DIR *dir;
> > +    Error *err = NULL;
> >
> >      if (o->have_children) {
> >          return;
> > @@ -662,16 +596,19 @@ static void usb_mtp_object_readdir(MTPState *s, MTPObject *o)
> >      if (!dir) {
> >          return;
> >      }
> > -#ifdef CONFIG_INOTIFY1
> > -    int watchfd = usb_mtp_add_watch(s->inotifyfd, o->path);
> > +
> > +    int watchfd = qemu_file_monitor_add_watch(s->file_monitor, o->path, NULL,
> > +                                              file_monitor_event, s, &err);
> 
> There is an add_watch(), but I don't see the corresponding
> remove_watch(). This may probably cause crashes if MTPState is freed.

Yes, I've added code to remove the watch, and also
to use a private file_monitor instance so free'ing
that will release all watches as a safety net.

> 
> >      if (watchfd == -1) {
> > -        fprintf(stderr, "usb-mtp: failed to add watch for %s\n", o->path);
> > +        fprintf(stderr, "usb-mtp: failed to add watch for %s: %s\n", o->path,
> > +                error_get_pretty(err));
> 
> maybe it's a good time to turn into error_report() ?

Yep


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks
  2018-11-07 22:23   ` Marc-André Lureau
@ 2018-11-13 17:11     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-13 17:11 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Thu, Nov 08, 2018 at 02:23:48AM +0400, Marc-André Lureau wrote:
> On Fri, Oct 19, 2018 at 5:49 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > In many cases a single VM will just need to whilelist a single identity
> > as the allowed user of network services. This is especially the case for
> > TLS live migration (optionally with NBD storage) where we just need to
> > whitelist the x509 certificate distinguished name of the source QEMU
> > host.
> >
> > Via QMP this can be configured with:
> >
> >   {
> >     "execute": "object-add",
> >     "arguments": {
> >       "qom-type": "authz-simple",
> >       "id": "authz0",
> >       "parameters": {
> >         "identity": "fred"
> >       }
> >     }
> >   }
> >
> > Or via the command line
> >
> >   -object authz-simple,id=authz0,identity=fred
> >
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> 
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>
> 
> (a test would be trivial)

Yes, will add one


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list
  2018-11-07 22:23   ` Marc-André Lureau
  2018-11-07 22:38     ` Eric Blake
@ 2018-11-13 17:29     ` Daniel P. Berrangé
  1 sibling, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-13 17:29 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Thu, Nov 08, 2018 at 02:23:43AM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Fri, Oct 19, 2018 at 5:45 PM Daniel P. Berrangé <berrange@redhat.com> wrote
> > ---
> >  Makefile                |   7 +-
> >  Makefile.objs           |   4 +
> >  qapi/authz.json         |  58 ++++++++
> >  qapi/qapi-schema.json   |   1 +
> >  include/authz/list.h    | 106 ++++++++++++++
> >  authz/list.c            | 309 ++++++++++++++++++++++++++++++++++++++++
> >  tests/test-authz-list.c | 171 ++++++++++++++++++++++
> >  .gitignore              |   4 +
> >  MAINTAINERS             |   1 +
> >  authz/Makefile.objs     |   1 +
> >  authz/trace-events      |   4 +
> >  tests/Makefile.include  |   4 +
> >  12 files changed, 669 insertions(+), 1 deletion(-)
> >  create mode 100644 qapi/authz.json
> >  create mode 100644 include/authz/list.h
> >  create mode 100644 authz/list.c
> >  create mode 100644 tests/test-authz-list.c
> >

> > diff --git a/qapi/authz.json b/qapi/authz.json
> > new file mode 100644
> > index 0000000000..607839c627
> > --- /dev/null
> > +++ b/qapi/authz.json
> > @@ -0,0 +1,58 @@
> > +# -*- Mode: Python -*-
> > +#
> > +# QAPI authz definitions
> > +
> > +##
> > +# @QAuthZListPolicy:
> > +#
> > +# The authorization policy result
> > +#
> > +# @deny: deny access
> > +# @allow: allow access
> > +#
> > +# Since: 3.0
> 
> obviously, you'll have to update to 3.2

4.0 in fact since we bump at the start of each year


> > +##
> > +# @QAuthZListRuleListHack:
> > +#
> > +# Not exposed via QMP; hack to generate QAuthZListRuleList
> > +# for use internally by the code.
> 
> Well, this will probably end in the documentation (it's already in the
> .json, which is one source of documentation ;).
> 
> What about adding a 'gen-list' field, or a 'pragma' listing the
> structs that should have list code generated?

I'll leave that for future motivated contributors, as I'm just
following pre-existing best practice here.


> > +static bool qauthz_list_is_allowed(QAuthZ *authz,
> > +                                   const char *identity,
> > +                                   Error **errp)
> > +{
> > +    QAuthZList *lauthz = QAUTHZ_LIST(authz);
> > +    QAuthZListRuleList *rules = lauthz->rules;
> > +
> > +    while (rules) {
> > +        QAuthZListRule *rule = rules->value;
> > +        QAuthZListFormat format = rule->has_format ? rule->format :
> > +            QAUTHZ_LIST_FORMAT_EXACT;
> > +
> > +        trace_qauthz_list_check_rule(authz, rule->match, identity,
> > +                                     format, rule->policy);
> > +        switch (format) {
> > +        case QAUTHZ_LIST_FORMAT_EXACT:
> > +            if (strcmp(rule->match, identity) == 0) {
> 
> g_str_equal() ?

Yes.

> 
> > +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> > +            }
> > +            break;
> > +#ifdef CONFIG_FNMATCH
> > +        case QAUTHZ_LIST_FORMAT_GLOB:
> > +            if (fnmatch(rule->match, identity, 0) == 0) {
> 
> Would GPatternSpec be a good alternative?

Excellent, I didn't know about it

> > +                return rule->policy == QAUTHZ_LIST_POLICY_ALLOW;
> > +            }
> > +            break;
> > +#else
> > +            return false;
> > +#endif
> > +        default:
> 
> 
> No g_warn_if_reached() ?Then perhaps add a comment why.

I guess we could.

> > +ssize_t qauthz_list_delete_rule(QAuthZList *auth, const char *match)
> > +{
> > +    QAuthZListRule *rule;
> > +    QAuthZListRuleList *rules, *prev;
> > +    size_t i = 0;
> > +
> > +    prev = NULL;
> > +    rules = auth->rules;
> > +    while (rules) {
> > +        rule = rules->value;
> > +        if (g_str_equal(rule->match, match)) {
> > +            if (prev) {
> > +                prev->next = rules->next;
> > +            } else {
> > +                auth->rules = rules->next;
> > +            }
> > +            rules->next = NULL;
> > +            qapi_free_QAuthZListRuleList(rules);
> > +            return i;
> 
> What's the point in returning the old index? Maybe true/false along
> with an Error would be more convenient?

It is required for the conversion of the existing acl_remove API
in the HMP monitor.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation
  2018-11-08  8:15   ` Marc-André Lureau
@ 2018-11-14 16:45     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-14 16:45 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Thu, Nov 08, 2018 at 12:15:54PM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Fri, Oct 19, 2018 at 5:51 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > From: "Daniel P. Berrange" <berrange@redhat.com>
> >
> > The 'qemu_acl' type was a previous non-QOM based attempt to provide an
> > authorization facility in QEMU. Because it is non-QOM based it cannot be
> > created via the command line and requires special monitor commands to
> > manipulate it.
> >
> > The new QAuthZ subclasses provide a superset of the functionality in
> > qemu_acl, so the latter can now be deleted. The HMP 'acl_*' monitor
> > commands are converted to use the new QAuthZSimple data type instead
> > in order to provide temporary backwards compatibility.
> >
> > Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> > +    monitor_printf(mon, "policy: %s\n",
> > +                   QAuthZListPolicy_lookup.array[auth->policy]);
> 
> please use QAuthZListPolicy_str()
> 
> > +
> > +    rules = auth->rules;
> > +    while (rules) {
> > +        QAuthZListRule *rule = rules->value;
> > +        i++;
> > +        monitor_printf(mon, "%zu: %s %s\n", i,
> > +                       QAuthZListPolicy_lookup.array[rule->policy],
> 
> QAuthZListPolicy_str

Yes.


> > @@ -163,12 +165,19 @@ static int vnc_auth_sasl_check_access(VncState *vs)
> >      vs->sasl.username = g_strdup((const char*)val);
> >      trace_vnc_auth_sasl_username(vs, vs->sasl.username);
> >
> > -    if (vs->vd->sasl.acl == NULL) {
> > +    if (vs->vd->sasl.authzid == NULL) {
> >          trace_vnc_auth_sasl_acl(vs, 1);
> >          return 0;
> >      }
> >
> > -    allow = qemu_acl_party_is_allowed(vs->vd->sasl.acl, vs->sasl.username);
> > +    allow = qauthz_is_allowed_by_id(vs->vd->sasl.authzid,
> > +                                    vs->sasl.username, &err);
> 
> Why not use qauthz_is_allowed() with .authz ?

The .authz object is only non-NULL when using the legacy "-vnc ..,acl"
flag syntax. When using the modern syntax (introduced by the followup
series mentioned in the cover letter) we want to resolve "authzid"
every time. This allows the user to safely delete & recreate the
authorization objects on the fly.


Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM
  2018-11-07 22:23   ` Marc-André Lureau
@ 2018-11-15 10:32     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-15 10:32 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Thu, Nov 08, 2018 at 02:23:18AM +0400, Marc-André Lureau wrote:
> Hi
> 
> On Fri, Oct 19, 2018 at 5:47 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> > diff --git a/qemu-options.hx b/qemu-options.hx
> > index a1c3e0e59c..a9654b8115 100644
> > --- a/qemu-options.hx
> > +++ b/qemu-options.hx
> > @@ -4447,6 +4447,41 @@ would look like:
> >       ...
> >  @end example
> >
> > +@item -object authz-pam,id=@var{id},service=@var{string}
> > +
> > +Create an authorization object that will control access to network services.
> > +
> > +The @option{service} parameter provides the name of a PAM service to use
> > +for authorization. It requires that a file @code{/etc/pam.d/@var{service}}
> > +exist to provide the configuration for the @code{account} subsystem.
> > +
> > +An example authorization object to validate a TLS x509 distinguished
> > +name would look like:
> > +
> > +@example
> > + # $QEMU \
> > +     ...
> > +     -object authz-simple,id=auth0,service=qemu-vnc
> 
> oops, wrong example,

Heh, fixed.

> 
> other than that,
> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

* Re: [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file access control list
  2018-11-07 22:23   ` Marc-André Lureau
@ 2018-11-15 10:33     ` Daniel P. Berrangé
  0 siblings, 0 replies; 36+ messages in thread
From: Daniel P. Berrangé @ 2018-11-15 10:33 UTC (permalink / raw)
  To: Marc-André Lureau
  Cc: QEMU, Markus Armbruster, Dr. David Alan Gilbert, Gerd Hoffmann, philmd

On Thu, Nov 08, 2018 at 02:23:34AM +0400, Marc-André Lureau wrote:
> On Fri, Oct 19, 2018 at 5:42 PM Daniel P. Berrangé <berrange@redhat.com> wrote:
> >
> > Add a QAuthZListFile object type that implements the QAuthZ interface. This
> > built-in implementation is a proxy around the QAtuhZList object type,
> > initializing it from an external file, and optionally, automatically
> > reloading it whenever it changes.
> >
> > To create an instance of this object via the QMP monitor, the syntax
> > used would be:
> >
> >       {
> >         "execute": "object-add",
> >         "arguments": {
> >           "qom-type": "authz-list-file",
> >           "id": "authz0",
> >           "parameters": {
> >             "filename": "/etc/qemu/vnc.acl",
> >             "refresh": "yes"
> >           }
> >         }
> >       }
> >
> > If "refresh" is "yes", inotify is used to monitor the file,
> > automatically reloading changes. If an error occurs during reloading,
> > all authorizations will fail until the file is next successfully
> > loaded.
> >
> > The /etc/qemu/vnc.acl file would contain a JSON representation of a
> > QAuthZList object
> >
> >     {
> >       "rules": [
> >          { "match": "fred", "policy": "allow", "format": "exact" },
> >          { "match": "bob", "policy": "allow", "format": "exact" },
> >          { "match": "danb", "policy": "deny", "format": "glob" },
> >          { "match": "dan*", "policy": "allow", "format": "exact" },
> >       ],
> >       "policy": "deny"
> >     }
> >
> > This sets up an authorization rule that allows 'fred', 'bob' and anyone
> > whose name starts with 'dan', except for 'danb'. Everyone unmatched is
> > denied.
> >
> > The object can be loaded on the comand line using
> >
> >    -object authz-list-file,id=authz0,filename=/etc/qemu/vnc.acl,refresh=yes
> >
> > Signed-off-by: Daniel P. Berrangé <berrange@redhat.com>
> > ---
> >  include/authz/listfile.h | 110 +++++++++++++++
> >  authz/listfile.c         | 286 +++++++++++++++++++++++++++++++++++++++
> >  authz/Makefile.objs      |   1 +
> >  authz/trace-events       |   4 +
> >  qemu-options.hx          |  46 +++++++
> >  5 files changed, 447 insertions(+)
> >  create mode 100644 include/authz/listfile.h
> >  create mode 100644 authz/listfile.c

> > +static void
> > +qauthz_list_file_prop_set_filename(Object *obj,
> > +                                   const char *value,
> > +                                   Error **errp G_GNUC_UNUSED)
> > +{
> > +    QAuthZListFile *fauthz = QAUTHZ_LIST_FILE(obj);
> > +
> > +    fauthz->filename = g_strdup(value);
> 
> Either prevent from modifying the filename, or free the exisiting value.

I'm freeing existing value.

> 
> other than that (and the lack of test)

...and adding a test

> Reviewed-by: Marc-André Lureau <marcandre.lureau@redhat.com>

Regards,
Daniel
-- 
|: https://berrange.com      -o-    https://www.flickr.com/photos/dberrange :|
|: https://libvirt.org         -o-            https://fstop138.berrange.com :|
|: https://entangle-photo.org    -o-    https://www.instagram.com/dberrange :|

^ permalink raw reply	[flat|nested] 36+ messages in thread

end of thread, other threads:[~2018-11-15 10:33 UTC | newest]

Thread overview: 36+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-19 13:38 [Qemu-devel] [PATCH v6 00/11] Add a standard authorization framework Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 01/11] util: add helper APIs for dealing with inotify in portable manner Daniel P. Berrangé
2018-11-07 18:08   ` Marc-André Lureau
2018-11-12 16:49     ` Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 02/11] qom: don't require user creatable objects to be registered Daniel P. Berrangé
2018-11-07 18:09   ` Marc-André Lureau
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 03/11] hw/usb: don't set IN_ISDIR for inotify watch in MTP driver Daniel P. Berrangé
2018-11-07 18:10   ` Marc-André Lureau
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 04/11] hw/usb: fix const-ness for string params " Daniel P. Berrangé
2018-11-07 18:11   ` Marc-André Lureau
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 05/11] hw/usb: switch MTP to use new inotify APIs Daniel P. Berrangé
2018-11-07 18:26   ` Marc-André Lureau
2018-11-13 17:07     ` Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 06/11] authz: add QAuthZ object as an authorization base class Daniel P. Berrangé
2018-11-07 22:23   ` Marc-André Lureau
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 07/11] authz: add QAuthZSimple object type for easy whitelist auth checks Daniel P. Berrangé
2018-10-22 23:54   ` Philippe Mathieu-Daudé
2018-11-07 22:23   ` Marc-André Lureau
2018-11-13 17:11     ` Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 08/11] authz: add QAuthZList object type for an access control list Daniel P. Berrangé
2018-10-23 10:18   ` Philippe Mathieu-Daudé
2018-11-07 22:23   ` Marc-André Lureau
2018-11-07 22:38     ` Eric Blake
2018-11-13 17:29     ` Daniel P. Berrangé
2018-11-08  8:18   ` Marc-André Lureau
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 09/11] authz: add QAuthZListFile object type for a file " Daniel P. Berrangé
2018-10-22 23:56   ` Philippe Mathieu-Daudé
2018-11-07 22:23   ` Marc-André Lureau
2018-11-15 10:33     ` Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 10/11] authz: add QAuthZPAM object type for authorizing using PAM Daniel P. Berrangé
2018-11-07 22:23   ` Marc-André Lureau
2018-11-15 10:32     ` Daniel P. Berrangé
2018-10-19 13:38 ` [Qemu-devel] [PATCH v6 11/11] authz: delete existing ACL implementation Daniel P. Berrangé
2018-10-23 11:14   ` Philippe Mathieu-Daudé
2018-11-08  8:15   ` Marc-André Lureau
2018-11-14 16:45     ` Daniel P. Berrangé

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.