diff mbox

[v3,05/10] util: add QAuthZSimple object type for a simple access control list

Message ID 1457636396-24983-5-git-send-email-berrange@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Daniel P. Berrangé March 10, 2016, 6:59 p.m. UTC
Add a QAuthZSimple object type that implements the QAuthZ
interface. This simple 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-simple",
      "id": "auth0",
      "parameters": {
        "rules": [
           { "match": "fred", "policy": "allow" },
           { "match": "bob", "policy": "allow" },
           { "match": "danb", "policy": "deny" },
           { "match": "dan*", "policy": "allow" }
        ],
        "policy": "deny"
      }
    }
  }

Or via the -object command line

  $QEMU \
     -object authz-simple,id=acl0,policy=deny,\
             match.0.name=fred,match.0.policy=allow, \
             match.1.name=bob,match.1.policy=allow, \
             match.2.name=danb,match.2.policy=deny, \
             match.3.name=dan*,match.3.policy=allow

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

Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
---
 Makefile                    |   2 +-
 include/qemu/authz-simple.h | 107 ++++++++++++++++++
 qapi-schema.json            |   6 +-
 qapi/util.json              |  31 ++++++
 tests/.gitignore            |   1 +
 tests/Makefile              |   3 +
 tests/test-authz-simple.c   | 156 +++++++++++++++++++++++++++
 util/Makefile.objs          |   1 +
 util/authz-simple.c         | 256 ++++++++++++++++++++++++++++++++++++++++++++
 9 files changed, 561 insertions(+), 2 deletions(-)
 create mode 100644 include/qemu/authz-simple.h
 create mode 100644 qapi/util.json
 create mode 100644 tests/test-authz-simple.c
 create mode 100644 util/authz-simple.c

Comments

Eric Blake March 22, 2016, 5:38 p.m. UTC | #1
On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
> Add a QAuthZSimple object type that implements the QAuthZ
> interface. This simple 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-simple",
>       "id": "auth0",
>       "parameters": {
>         "rules": [
>            { "match": "fred", "policy": "allow" },
>            { "match": "bob", "policy": "allow" },
>            { "match": "danb", "policy": "deny" },
>            { "match": "dan*", "policy": "allow" }
>         ],
>         "policy": "deny"
>       }
>     }
>   }

So "match" appears to be a glob (as opposed to a regex).  And order is
important (the first rule matched ends the lookup), so you correctly
used an array for the list of rules (a dict does not have to maintain
order).

> 
> Or via the -object command line
> 
>   $QEMU \
>      -object authz-simple,id=acl0,policy=deny,\
>              match.0.name=fred,match.0.policy=allow, \
>              match.1.name=bob,match.1.policy=allow, \
>              match.2.name=danb,match.2.policy=deny, \
>              match.3.name=dan*,match.3.policy=allow

The '*' in the command line will require shell quoting.

> 
> This sets up an authorization rule that allows 'fred',
> 'bob' and anyone whose name starts with 'dan', except
> for 'danb'. Everyone unmatched is denied.
> 
> Signed-off-by: Daniel P. Berrange <berrange@redhat.com>
> ---

> +/**
> + * QAuthZSimple:
> + *
> + * This authorization driver provides a simple 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 instace of this class via QMP:

s/instace/instance/

> + *
> + * Or via the CLI:
> + *
> + *   $QEMU                                                  \
> + *    -object authz-simple,id=acl0,policy=deny,             \
> + *            match.0.name=fred,match.0.policy=allow,       \
> + *            match.1.name=bob,match.1.policy=allow,        \
> + *            match.2.name=danb,match.2.policy=deny,        \
> + *            match.3.name=dan*,match.3.policy=allow

Again, quoting the "*" is important, and maybe a comment that the
whitespace is for display purposes but must be omitted on the command line.

> +++ b/qapi-schema.json
> @@ -5,6 +5,9 @@
>  # QAPI common definitions
>  { 'include': 'qapi/common.json' }
>  
> +# QAPI util definitions
> +{ 'include': 'qapi/util.json' }
> +
>  # QAPI crypto definitions
>  { 'include': 'qapi/crypto.json' }
>  
> @@ -3652,7 +3655,8 @@
>  # Since 2.5
>  ##
>  { 'struct': 'DummyForceArrays',
> -  'data': { 'unused': ['X86CPUFeatureWordInfo'] } }
> +  'data': { 'unused': ['X86CPUFeatureWordInfo'],
> +            'iamalsounused': ['QAuthZSimpleRule'] } }

Cute.  I might have renamed things and gone 'unused1' and 'unused2'.

> +++ b/qapi/util.json
> @@ -0,0 +1,31 @@
> +# -*- Mode: Python -*-
> +#
> +# QAPI util definitions
> +
> +##
> +# QAuthZSimplePolicy:
> +#
> +# The authorization policy result
> +#
> +# @deny: deny access
> +# @allow: allow access
> +#
> +# Since: 2.6
> +##
> +{ 'enum': 'QAuthZSimplePolicy',
> +  'prefix': 'QAUTHZ_SIMPLE_POLICY',
> +  'data': ['deny', 'allow']}

I know Markus isn't a big fan of 'prefix', but I don't mind it.

We're awfully late in the 2.6 cycle; this feels like a feature addition
rather than a bug fix, so should it be 2.7?

> +
> +##
> +# QAuthZSimpleRule:
> +#
> +# A single authorization rule.
> +#
> +# @match: a glob to match against a user identity
> +# @policy: the result to return if @match evaluates to true
> +#
> +# Since: 2.6
> +##
> +{ 'struct': 'QAuthZSimpleRule',
> +  'data': {'match': 'str',
> +           'policy': 'QAuthZSimplePolicy'}}

Hmm. I was expecting something like:

{ 'struct': 'QAuthZSimple',
  'data': { 'rules': [ 'QAuthZSimpleRule' ],
            'policy': 'QAuthZSimplePolicy' } }

but I guess that's one of our short-comings of QOM: the top-level
structure does not have to be specified anywhere in QAPI.

> +++ b/tests/test-authz-simple.c
> @@ -0,0 +1,156 @@
> +/*
> + * QEMU simple authorization object
> + *
> + * Copyright (c) 2016 Red Hat, Inc.
> + *

> +static void test_authz_default_deny(void)
> +{
> +    QAuthZSimple *auth = qauthz_simple_new("auth0",
> +                                           QAUTHZ_SIMPLE_POLICY_DENY,
> +                                           &error_abort);
> +
> +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> +

Okay, so you definitely intend to return false without setting errp, so
it is a ternary result.

> +
> +#ifdef CONFIG_FNMATCH
> +static void test_authz_complex(void)
> +{

Wait - the behavior depends on whether fnmatch() is available?  That is,
a name is a literal match if fnmatch() is not present, and glob if
present?  I'd argue that if fnmatch() is not present, we must explicitly
reject any "match" with glob metacharacters, rather than accidentally
matching only a literal "*" when a glob was intended.

> +++ b/util/authz-simple.c

> +
> +/* If no fnmatch, fallback to exact string matching
> + * instead of allowing wildcards */
> +#ifdef CONFIG_FNMATCH
> +#include <fnmatch.h>
> +#define qauthz_match(pattern, string) fnmatch(pattern, string, 0)
> +#else
> +#define qauthz_match(pattern, string) strcmp(pattern, string)
> +#endif

As mentioned above, I'd be more comfortable if you also explicitly
reject any attempt to add a pattern that resembles a glob when globbing
is not enabled.  Or maybe it's worth a more complex QAPI definition:

# How to interpret 'match', as a literal string or a glob
{ 'enum': 'QAuthZSimpleStyle', 'data': [ 'literal', 'glob' ] }
# @style: #optional, default to 'literal'
{ 'struct': 'QAuthZSimpleRule',
  'data': { 'match': 'str', '*style': 'QAuthZSimpleStyle',
            'policy': 'QAuthZSimplePolicy' } }

and used as:

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

where you can then gracefully error out for ALL attempts to use
"style":"glob" if fnmatch() is not present, and use strcmp() even when
fnmatch() is present for rules that have the (default) "style":"literal".


> +static void
> +qauthz_simple_class_init(ObjectClass *oc, void *data)
> +{
> +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> +
> +    ucc->complete = qauthz_simple_complete;
> +    authz->is_allowed = qauthz_simple_is_allowed;
> +
> +    object_class_property_add_enum(oc, "policy",
> +                                   "QAuthZSimplePolicy",
> +                                   QAuthZSimplePolicy_lookup,
> +                                   qauthz_simple_prop_get_policy,
> +                                   qauthz_simple_prop_set_policy,
> +                                   NULL);
> +
> +    object_class_property_add(oc, "rules", "QAuthZSimpleRule",
> +                              qauthz_simple_prop_get_rules,
> +                              qauthz_simple_prop_set_rules,
> +                              NULL, NULL, NULL);
> +}

Not for this patch, but it would be cool if we had enough framework to
just tell QOM to instantiate an object with callbacks by merely pointing
to the name of a QAPI type that the object implements (referring back to
my comment that I'm a bit surprised we didn't need a QAPI type for the
overall QAuthZSimple).

> +
> +size_t qauthz_simple_append_rule(QAuthZSimple *auth, const char *match,
> +                                 QAuthZSimplePolicy policy)
> +{
> +    QAuthZSimpleRule *rule;
> +    QAuthZSimpleRuleList *rules, *tmp;
> +    size_t i = 0;
> +
> +    rule = g_new0(QAuthZSimpleRule, 1);
> +    rule->policy = policy;
> +    rule->match = g_strdup(match);
> +
> +    tmp = g_new0(QAuthZSimpleRuleList, 1);
> +    tmp->value = rule;
> +
> +    rules = auth->rules;
> +    if (rules) {
> +        while (rules->next) {
> +            i++;
> +            rules = rules->next;
> +        }
> +        rules->next = tmp;
> +        return i + 1;

No checks whether 'match' is already an existing rule...


> +ssize_t qauthz_simple_delete_rule(QAuthZSimple *auth, const char *match)
> +{
> +    QAuthZSimpleRule *rule;
> +    QAuthZSimpleRuleList *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_QAuthZSimpleRuleList(rules);
> +            return i;

...which means a rule can be inserted more than once, and this only
removes the first instance of the rule.  Do we care enough to add in
additional checking that we aren't permitting duplicate 'match' strings
in the list of rules?

If you add the style=literal/glob to a rule, would you also want to be
able to constrain deletion to a particular style (delete the glob 'dan*'
but leave the literal 'dan*' intact)?
Daniel P. Berrangé March 23, 2016, 12:38 p.m. UTC | #2
On Tue, Mar 22, 2016 at 11:38:53AM -0600, Eric Blake wrote:
> On 03/10/2016 11:59 AM, Daniel P. Berrange wrote:
> > Add a QAuthZSimple object type that implements the QAuthZ
> > interface. This simple 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-simple",
> >       "id": "auth0",
> >       "parameters": {
> >         "rules": [
> >            { "match": "fred", "policy": "allow" },
> >            { "match": "bob", "policy": "allow" },
> >            { "match": "danb", "policy": "deny" },
> >            { "match": "dan*", "policy": "allow" }
> >         ],
> >         "policy": "deny"
> >       }
> >     }
> >   }
> 
> So "match" appears to be a glob (as opposed to a regex).  And order is
> important (the first rule matched ends the lookup), so you correctly
> used an array for the list of rules (a dict does not have to maintain
> order).

Yes, its a glob (as defined by fnmatch)

> > 
> > Or via the -object command line
> > 
> >   $QEMU \
> >      -object authz-simple,id=acl0,policy=deny,\
> >              match.0.name=fred,match.0.policy=allow, \
> >              match.1.name=bob,match.1.policy=allow, \
> >              match.2.name=danb,match.2.policy=deny, \
> >              match.3.name=dan*,match.3.policy=allow
> 
> The '*' in the command line will require shell quoting.

Yeah, CLI syntax escaping from shell is horrible, but fortunately
libvirt doesn't use shell :-) None the less I'll update the
example.


> > +##
> > +# QAuthZSimplePolicy:
> > +#
> > +# The authorization policy result
> > +#
> > +# @deny: deny access
> > +# @allow: allow access
> > +#
> > +# Since: 2.6
> > +##
> > +{ 'enum': 'QAuthZSimplePolicy',
> > +  'prefix': 'QAUTHZ_SIMPLE_POLICY',
> > +  'data': ['deny', 'allow']}
> 
> I know Markus isn't a big fan of 'prefix', but I don't mind it.

It doesn't affect public API anyway, so we can change it at will
later if desired.

> We're awfully late in the 2.6 cycle; this feels like a feature addition
> rather than a bug fix, so should it be 2.7?

It is definitely a feature addition. I was hoping to get it into 2.6
since it is logically associated with the TLS enhancements I've been
doing.  It isn't the end of the world if it waits until 2.7 though,
so I'm open minded either way.

Basically TLS provides encryption, x509 certs provide authentication,
and this ACL stuff provides the authorization piece of the puzzel.

If we miss the authorization piece in 2.6 we're still in a far better
position than we were historically because we at least have encryption
and authentication still :-)  You can mitigate the lack of authorization
by having a dedicated CA certificate just for QEMU services, so that
you limit who has access to client certs. This is a crude authorization
setup that is none the less acceptable in many scenarios.

> > +##
> > +# QAuthZSimpleRule:
> > +#
> > +# A single authorization rule.
> > +#
> > +# @match: a glob to match against a user identity
> > +# @policy: the result to return if @match evaluates to true
> > +#
> > +# Since: 2.6
> > +##
> > +{ 'struct': 'QAuthZSimpleRule',
> > +  'data': {'match': 'str',
> > +           'policy': 'QAuthZSimplePolicy'}}
> 
> Hmm. I was expecting something like:
> 
> { 'struct': 'QAuthZSimple',
>   'data': { 'rules': [ 'QAuthZSimpleRule' ],
>             'policy': 'QAuthZSimplePolicy' } }
> 
> but I guess that's one of our short-comings of QOM: the top-level
> structure does not have to be specified anywhere in QAPI.

I'm not sure I'd call it a short-coming but rather its just a difference
in approach. For anything that is a user-defined object, the QAPI schema
is defined implicitly by the QOM object or class definition. With the
ability to define QOM properties against the class instad of object,
we are actally in a position where we could auto-generate a QAPI schema
to represent each user-defined object type if desired. Alternatively
we could equally extend QAPI to have an 'object' type (which is a
specialization of the 'struct' type) and auto-generate the basic
boilerplate code to define a QOM object class from it.

> > +static void test_authz_default_deny(void)
> > +{
> > +    QAuthZSimple *auth = qauthz_simple_new("auth0",
> > +                                           QAUTHZ_SIMPLE_POLICY_DENY,
> > +                                           &error_abort);
> > +
> > +    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
> > +
> 
> Okay, so you definitely intend to return false without setting errp, so
> it is a ternary result.

Yep

> > +#ifdef CONFIG_FNMATCH
> > +static void test_authz_complex(void)
> > +{
> 
> Wait - the behavior depends on whether fnmatch() is available?  That is,
> a name is a literal match if fnmatch() is not present, and glob if
> present?  I'd argue that if fnmatch() is not present, we must explicitly
> reject any "match" with glob metacharacters, rather than accidentally
> matching only a literal "*" when a glob was intended.

Historically we have the qemu/acl.c code which uses fnmatch and falls
back to plain strcmp if not available. Since this was intended to be an
exact drop-in replacement, I tried to implement the same logic here.

We could do something slightly different though - define a property
against the QAuthzSimple object which enumerates the type of matching
scheme - exact, glob or regex. The acl.c compatibility layer could
then simply set  matchtype=exact when !CONFIG_FNMATCH, which would
in turn allow the QAuthzSimple impl to raise a fatall error for use
of globbing when fnmatch is missing.

> # How to interpret 'match', as a literal string or a glob
> { 'enum': 'QAuthZSimpleStyle', 'data': [ 'literal', 'glob' ] }
> # @style: #optional, default to 'literal'
> { 'struct': 'QAuthZSimpleRule',
>   'data': { 'match': 'str', '*style': 'QAuthZSimpleStyle',
>             'policy': 'QAuthZSimplePolicy' } }
> 
> and used as:
> 
>          "rules": [
>             { "match": "fred", "policy": "allow" },
>             { "match": "bob", "policy": "allow" },
>             { "match": "danb", "policy": "deny" },
>             { "match": "dan*", "policy": "allow", "style": "glob" }
> 
> where you can then gracefully error out for ALL attempts to use
> "style":"glob" if fnmatch() is not present, and use strcmp() even when
> fnmatch() is present for rules that have the (default) "style":"literal".

Yep, allowing style to be set per rule is another option - I wonder
if it is better todo it per-rule or per-ACL applying to all rules.

> > +static void
> > +qauthz_simple_class_init(ObjectClass *oc, void *data)
> > +{
> > +    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
> > +    QAuthZClass *authz = QAUTHZ_CLASS(oc);
> > +
> > +    ucc->complete = qauthz_simple_complete;
> > +    authz->is_allowed = qauthz_simple_is_allowed;
> > +
> > +    object_class_property_add_enum(oc, "policy",
> > +                                   "QAuthZSimplePolicy",
> > +                                   QAuthZSimplePolicy_lookup,
> > +                                   qauthz_simple_prop_get_policy,
> > +                                   qauthz_simple_prop_set_policy,
> > +                                   NULL);
> > +
> > +    object_class_property_add(oc, "rules", "QAuthZSimpleRule",
> > +                              qauthz_simple_prop_get_rules,
> > +                              qauthz_simple_prop_set_rules,
> > +                              NULL, NULL, NULL);
> > +}
> 
> Not for this patch, but it would be cool if we had enough framework to
> just tell QOM to instantiate an object with callbacks by merely pointing
> to the name of a QAPI type that the object implements (referring back to
> my comment that I'm a bit surprised we didn't need a QAPI type for the
> overall QAuthZSimple).

Yep, as mentioned above this is just the difference between QAPI
and QOM user creatable objects that needs resolving somehow.

> > +size_t qauthz_simple_append_rule(QAuthZSimple *auth, const char *match,
> > +                                 QAuthZSimplePolicy policy)
> > +{
> > +    QAuthZSimpleRule *rule;
> > +    QAuthZSimpleRuleList *rules, *tmp;
> > +    size_t i = 0;
> > +
> > +    rule = g_new0(QAuthZSimpleRule, 1);
> > +    rule->policy = policy;
> > +    rule->match = g_strdup(match);
> > +
> > +    tmp = g_new0(QAuthZSimpleRuleList, 1);
> > +    tmp->value = rule;
> > +
> > +    rules = auth->rules;
> > +    if (rules) {
> > +        while (rules->next) {
> > +            i++;
> > +            rules = rules->next;
> > +        }
> > +        rules->next = tmp;
> > +        return i + 1;
> 
> No checks whether 'match' is already an existing rule...
> 
> 
> > +ssize_t qauthz_simple_delete_rule(QAuthZSimple *auth, const char *match)
> > +{
> > +    QAuthZSimpleRule *rule;
> > +    QAuthZSimpleRuleList *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_QAuthZSimpleRuleList(rules);
> > +            return i;
> 
> ...which means a rule can be inserted more than once, and this only
> removes the first instance of the rule.  Do we care enough to add in
> additional checking that we aren't permitting duplicate 'match' strings
> in the list of rules?

I don't think it really matters much - if you add 2 rules with the
same string, you still need 2 calls to delete it. The order of
deletion can be left undefined I think.

Regards,
Daniel
diff mbox

Patch

diff --git a/Makefile b/Makefile
index 903dc35..60ad13e 100644
--- a/Makefile
+++ b/Makefile
@@ -274,7 +274,7 @@  qapi-modules = $(SRC_PATH)/qapi-schema.json $(SRC_PATH)/qapi/common.json \
                $(SRC_PATH)/qapi/block.json $(SRC_PATH)/qapi/block-core.json \
                $(SRC_PATH)/qapi/event.json $(SRC_PATH)/qapi/introspect.json \
                $(SRC_PATH)/qapi/crypto.json $(SRC_PATH)/qapi/rocker.json \
-               $(SRC_PATH)/qapi/trace.json
+               $(SRC_PATH)/qapi/trace.json $(SRC_PATH)/qapi/util.json
 
 qapi-types.c qapi-types.h :\
 $(qapi-modules) $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
diff --git a/include/qemu/authz-simple.h b/include/qemu/authz-simple.h
new file mode 100644
index 0000000..74c09e3
--- /dev/null
+++ b/include/qemu/authz-simple.h
@@ -0,0 +1,107 @@ 
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2016 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 "qemu/authz.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 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 instace of this class via QMP:
+ *
+ *  {
+ *    "execute": "object-add",
+ *    "arguments": {
+ *      "qom-type": "authz-simple",
+ *      "id": "auth0",
+ *      "parameters": {
+ *        "rules": [
+ *           { "match": "fred", "policy": "allow" },
+ *           { "match": "bob", "policy": "allow" },
+ *           { "match": "danb", "policy": "deny" },
+ *           { "match": "dan*", "policy": "allow" }
+ *        ],
+ *        "policy": "deny"
+ *      }
+ *    }
+ *  }
+ *
+ * Or via the CLI:
+ *
+ *   $QEMU                                                  \
+ *    -object authz-simple,id=acl0,policy=deny,             \
+ *            match.0.name=fred,match.0.policy=allow,       \
+ *            match.1.name=bob,match.1.policy=allow,        \
+ *            match.2.name=danb,match.2.policy=deny,        \
+ *            match.3.name=dan*,match.3.policy=allow
+ *
+ */
+struct QAuthZSimple {
+    QAuthZ parent_obj;
+
+    QAuthZSimplePolicy policy;
+    QAuthZSimpleRuleList *rules;
+};
+
+
+struct QAuthZSimpleClass {
+    QAuthZClass parent_class;
+};
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+                                QAuthZSimplePolicy policy,
+                                Error **errp);
+
+size_t qauthz_simple_append_rule(QAuthZSimple *auth, const char *match,
+                                 QAuthZSimplePolicy policy);
+
+size_t qauthz_simple_insert_rule(QAuthZSimple *auth, const char *match,
+                                 QAuthZSimplePolicy policy, size_t index);
+
+ssize_t qauthz_simple_delete_rule(QAuthZSimple *auth, const char *match);
+
+
+#endif /* QAUTHZ_SIMPLE_H__ */
+
diff --git a/qapi-schema.json b/qapi-schema.json
index 362c9d8..b6769de 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -5,6 +5,9 @@ 
 # QAPI common definitions
 { 'include': 'qapi/common.json' }
 
+# QAPI util definitions
+{ 'include': 'qapi/util.json' }
+
 # QAPI crypto definitions
 { 'include': 'qapi/crypto.json' }
 
@@ -3652,7 +3655,8 @@ 
 # Since 2.5
 ##
 { 'struct': 'DummyForceArrays',
-  'data': { 'unused': ['X86CPUFeatureWordInfo'] } }
+  'data': { 'unused': ['X86CPUFeatureWordInfo'],
+            'iamalsounused': ['QAuthZSimpleRule'] } }
 
 
 ##
diff --git a/qapi/util.json b/qapi/util.json
new file mode 100644
index 0000000..d903497
--- /dev/null
+++ b/qapi/util.json
@@ -0,0 +1,31 @@ 
+# -*- Mode: Python -*-
+#
+# QAPI util definitions
+
+##
+# QAuthZSimplePolicy:
+#
+# The authorization policy result
+#
+# @deny: deny access
+# @allow: allow access
+#
+# Since: 2.6
+##
+{ 'enum': 'QAuthZSimplePolicy',
+  'prefix': 'QAUTHZ_SIMPLE_POLICY',
+  'data': ['deny', 'allow']}
+
+##
+# QAuthZSimpleRule:
+#
+# A single authorization rule.
+#
+# @match: a glob to match against a user identity
+# @policy: the result to return if @match evaluates to true
+#
+# Since: 2.6
+##
+{ 'struct': 'QAuthZSimpleRule',
+  'data': {'match': 'str',
+           'policy': 'QAuthZSimplePolicy'}}
diff --git a/tests/.gitignore b/tests/.gitignore
index 787c95c..1e73a2f 100644
--- a/tests/.gitignore
+++ b/tests/.gitignore
@@ -8,6 +8,7 @@  check-qom-interface
 check-qom-proplist
 rcutorture
 test-aio
+test-authz-simple
 test-base64
 test-bitops
 test-blockjob-txn
diff --git a/tests/Makefile b/tests/Makefile
index cd4bbd4..7a1d959 100644
--- a/tests/Makefile
+++ b/tests/Makefile
@@ -92,6 +92,7 @@  check-unit-$(CONFIG_GNUTLS) += tests/test-io-channel-tls$(EXESUF)
 check-unit-y += tests/test-io-channel-command$(EXESUF)
 check-unit-y += tests/test-io-channel-buffer$(EXESUF)
 check-unit-y += tests/test-base64$(EXESUF)
+check-unit-y += tests/test-authz-simple$(EXESUF)
 
 check-block-$(CONFIG_POSIX) += tests/qemu-iotests-quick.sh
 
@@ -430,6 +431,8 @@  tests/test-timed-average$(EXESUF): tests/test-timed-average.o qemu-timer.o \
 	$(test-util-obj-y)
 tests/test-base64$(EXESUF): tests/test-base64.o \
 	libqemuutil.a libqemustub.a
+tests/test-authz-simple$(EXESUF): tests/test-authz-simple.o \
+	libqemuutil.a libqemustub.a $(util-qom-obj-y) $(qom-obj-y)
 
 tests/test-qapi-types.c tests/test-qapi-types.h :\
 $(SRC_PATH)/tests/qapi-schema/qapi-schema-test.json $(SRC_PATH)/scripts/qapi-types.py $(qapi-py)
diff --git a/tests/test-authz-simple.c b/tests/test-authz-simple.c
new file mode 100644
index 0000000..4b1dd0c
--- /dev/null
+++ b/tests/test-authz-simple.c
@@ -0,0 +1,156 @@ 
+/*
+ * QEMU simple authorization object
+ *
+ * Copyright (c) 2016 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 <glib.h>
+
+#include "qemu/authz-simple.h"
+
+static void test_authz_default_deny(void)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_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)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_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)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_POLICY_ALLOW,
+                                           &error_abort);
+
+    qauthz_simple_append_rule(auth, "fred", QAUTHZ_SIMPLE_POLICY_DENY);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+static void test_authz_explicit_allow(void)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_POLICY_DENY,
+                                           &error_abort);
+
+    qauthz_simple_append_rule(auth, "fred", QAUTHZ_SIMPLE_POLICY_ALLOW);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "fred", &error_abort));
+
+    object_unparent(OBJECT(auth));
+}
+
+
+#ifdef CONFIG_FNMATCH
+static void test_authz_complex(void)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_POLICY_DENY,
+                                           &error_abort);
+
+    qauthz_simple_append_rule(auth, "fred", QAUTHZ_SIMPLE_POLICY_ALLOW);
+    qauthz_simple_append_rule(auth, "bob", QAUTHZ_SIMPLE_POLICY_ALLOW);
+    qauthz_simple_append_rule(auth, "dan", QAUTHZ_SIMPLE_POLICY_DENY);
+    qauthz_simple_append_rule(auth, "dan*", QAUTHZ_SIMPLE_POLICY_ALLOW);
+
+    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));
+
+    object_unparent(OBJECT(auth));
+}
+#endif
+
+static void test_authz_add_remove(void)
+{
+    QAuthZSimple *auth = qauthz_simple_new("auth0",
+                                           QAUTHZ_SIMPLE_POLICY_DENY,
+                                           &error_abort);
+
+    g_assert_cmpint(qauthz_simple_append_rule(auth, "fred",
+                                              QAUTHZ_SIMPLE_POLICY_ALLOW),
+                    ==, 0);
+    g_assert_cmpint(qauthz_simple_append_rule(auth, "bob",
+                                              QAUTHZ_SIMPLE_POLICY_ALLOW),
+                    ==, 1);
+    g_assert_cmpint(qauthz_simple_append_rule(auth, "dan",
+                                              QAUTHZ_SIMPLE_POLICY_DENY),
+                    ==, 2);
+    g_assert_cmpint(qauthz_simple_append_rule(auth, "dan*",
+                                              QAUTHZ_SIMPLE_POLICY_ALLOW),
+                    ==, 3);
+
+    g_assert(!qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    g_assert_cmpint(qauthz_simple_delete_rule(auth, "dan"),
+                    ==, 2);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    g_assert_cmpint(qauthz_simple_append_rule(auth, "dan",
+                                              QAUTHZ_SIMPLE_POLICY_DENY),
+                    ==, 3);
+
+    g_assert(qauthz_is_allowed(QAUTHZ(auth), "dan", &error_abort));
+
+    g_assert_cmpint(qauthz_simple_delete_rule(auth, "dan"),
+                    ==, 3);
+
+    g_assert_cmpint(qauthz_simple_insert_rule(auth, "dan",
+                                              QAUTHZ_SIMPLE_POLICY_DENY, 2),
+                    ==, 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/simple/default/deny", test_authz_default_deny);
+    g_test_add_func("/auth/simple/default/allow", test_authz_default_allow);
+    g_test_add_func("/auth/simple/explicit/deny", test_authz_explicit_deny);
+    g_test_add_func("/auth/simple/explicit/allow", test_authz_explicit_allow);
+#ifdef CONFIG_FNMATCH
+    g_test_add_func("/auth/simple/complex", test_authz_complex);
+#endif
+    g_test_add_func("/auth/simple/add-remove", test_authz_add_remove);
+
+    return g_test_run();
+}
diff --git a/util/Makefile.objs b/util/Makefile.objs
index b29fb45..4870905 100644
--- a/util/Makefile.objs
+++ b/util/Makefile.objs
@@ -34,3 +34,4 @@  util-obj-y += base64.o
 util-obj-y += log.o
 
 util-qom-obj-y += authz.o
+util-qom-obj-y += authz-simple.o
diff --git a/util/authz-simple.c b/util/authz-simple.c
new file mode 100644
index 0000000..ab82f72
--- /dev/null
+++ b/util/authz-simple.c
@@ -0,0 +1,256 @@ 
+/*
+ * QEMU simple authorization driver
+ *
+ * Copyright (c) 2016 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/authz-simple.h"
+#include "qom/object_interfaces.h"
+#include "qapi-visit.h"
+
+/* If no fnmatch, fallback to exact string matching
+ * instead of allowing wildcards */
+#ifdef CONFIG_FNMATCH
+#include <fnmatch.h>
+#define qauthz_match(pattern, string) fnmatch(pattern, string, 0)
+#else
+#define qauthz_match(pattern, string) strcmp(pattern, string)
+#endif
+
+static bool qauthz_simple_is_allowed(QAuthZ *authz,
+                                     const char *identity,
+                                     Error **errp)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(authz);
+    QAuthZSimpleRuleList *rules = sauthz->rules;
+
+    while (rules) {
+        QAuthZSimpleRule *rule = rules->value;
+        if (qauthz_match(rule->match, identity) == 0) {
+            return rule->policy == QAUTHZ_SIMPLE_POLICY_ALLOW;
+        }
+        rules = rules->next;
+    }
+
+    return sauthz->policy == QAUTHZ_SIMPLE_POLICY_ALLOW;
+}
+
+
+static void
+qauthz_simple_prop_set_policy(Object *obj,
+                              int value,
+                              Error **errp G_GNUC_UNUSED)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    sauthz->policy = value;
+}
+
+
+static int
+qauthz_simple_prop_get_policy(Object *obj,
+                              Error **errp G_GNUC_UNUSED)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    return sauthz->policy;
+}
+
+
+static void
+qauthz_simple_prop_get_rules(Object *obj, Visitor *v, const char *name,
+                             void *opaque, Error **errp)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    visit_type_QAuthZSimpleRuleList(v, name, &sauthz->rules, errp);
+}
+
+static void
+qauthz_simple_prop_set_rules(Object *obj, Visitor *v, const char *name,
+                             void *opaque, Error **errp)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    qapi_free_QAuthZSimpleRuleList(sauthz->rules);
+    visit_type_QAuthZSimpleRuleList(v, name, &sauthz->rules, errp);
+}
+
+
+static void
+qauthz_simple_complete(UserCreatable *uc, Error **errp)
+{
+}
+
+
+static void
+qauthz_simple_finalize(Object *obj)
+{
+    QAuthZSimple *sauthz = QAUTHZ_SIMPLE(obj);
+
+    qapi_free_QAuthZSimpleRuleList(sauthz->rules);
+}
+
+
+static void
+qauthz_simple_class_init(ObjectClass *oc, void *data)
+{
+    UserCreatableClass *ucc = USER_CREATABLE_CLASS(oc);
+    QAuthZClass *authz = QAUTHZ_CLASS(oc);
+
+    ucc->complete = qauthz_simple_complete;
+    authz->is_allowed = qauthz_simple_is_allowed;
+
+    object_class_property_add_enum(oc, "policy",
+                                   "QAuthZSimplePolicy",
+                                   QAuthZSimplePolicy_lookup,
+                                   qauthz_simple_prop_get_policy,
+                                   qauthz_simple_prop_set_policy,
+                                   NULL);
+
+    object_class_property_add(oc, "rules", "QAuthZSimpleRule",
+                              qauthz_simple_prop_get_rules,
+                              qauthz_simple_prop_set_rules,
+                              NULL, NULL, NULL);
+}
+
+
+QAuthZSimple *qauthz_simple_new(const char *id,
+                                QAuthZSimplePolicy policy,
+                                Error **errp)
+{
+    return QAUTHZ_SIMPLE(
+        object_new_with_props(TYPE_QAUTHZ_SIMPLE,
+                              object_get_objects_root(),
+                              id, errp,
+                              "policy", QAuthZSimplePolicy_lookup[policy],
+                              NULL));
+}
+
+
+size_t qauthz_simple_append_rule(QAuthZSimple *auth, const char *match,
+                                 QAuthZSimplePolicy policy)
+{
+    QAuthZSimpleRule *rule;
+    QAuthZSimpleRuleList *rules, *tmp;
+    size_t i = 0;
+
+    rule = g_new0(QAuthZSimpleRule, 1);
+    rule->policy = policy;
+    rule->match = g_strdup(match);
+
+    tmp = g_new0(QAuthZSimpleRuleList, 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;
+    }
+}
+
+
+size_t qauthz_simple_insert_rule(QAuthZSimple *auth, const char *match,
+                                 QAuthZSimplePolicy policy, size_t index)
+{
+    QAuthZSimpleRule *rule;
+    QAuthZSimpleRuleList *rules, *tmp;
+    size_t i = 0;
+
+    rule = g_new0(QAuthZSimpleRule, 1);
+    rule->policy = policy;
+    rule->match = g_strdup(match);
+
+    tmp = g_new0(QAuthZSimpleRuleList, 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_simple_delete_rule(QAuthZSimple *auth, const char *match)
+{
+    QAuthZSimpleRule *rule;
+    QAuthZSimpleRuleList *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_QAuthZSimpleRuleList(rules);
+            return i;
+        }
+        prev = rules;
+        rules = rules->next;
+        i++;
+    }
+
+    return -1;
+}
+
+
+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);