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 <berra...@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 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); -- 2.5.0