Hi, Sorry for delaying this update so long because of some other works.
The ACL implementation has been improved. As we discussed, two new functionalities has been added: * The access control on attributes of elements * xpath based ACL. The schema and the corresponding codes has been simplified: <define name="element-acls"> <element name="acls"> <zeroOrMore> <choice> <element name="user"> <attribute name="id"><text/></attribute> <choice> <attribute name="role"><data type="IDREF"/></attribute> <zeroOrMore> <ref name="element-acl"/> </zeroOrMore> </choice> </element> <element name="role"> <attribute name="id"><data type="ID"/></attribute> <zeroOrMore> <ref name="element-acl"/> </zeroOrMore> </element> </choice> </zeroOrMore> </element> </define> <define name="element-acl"> <choice> <element name="read"> <ref name="attribute-acl"/> </element> <element name="write"> <ref name="attribute-acl"/> </element> <element name="deny"> <ref name="attribute-acl"/> </element> </choice> </define> <define name="attribute-acl"> <attribute name="id"><data type="ID"/></attribute> <choice> <attribute name="tag"><text/></attribute> <attribute name="ref"><data type="IDREF"/></attribute> <group> <attribute name="tag"><text/></attribute> <attribute name="ref"><data type="IDREF"/></attribute> </group> <attribute name="xpath"><text/></attribute> </choice> <optional> <attribute name="attribute"><text/></attribute> </optional> </define> A configuration example: .. <acls> <role id="operator"> <write id="operator-write-0" tag="nodes"/> <write id="operator-write-1" tag="status"/> </role> <role id="monitor"> <read id="monitor-read-0" tag="nodes"/> <read id="monitor-read-1" tag="status"/> </role> <user id="gaoyan"> <write id="gaoyan-write-0" xpath="//primiti...@id='rsc0']//nvpa...@name='target-role']"/> <deny id ="gaoyan-deny-0" xpath="//primiti...@id='rsc0']//nvpa...@name='password']" attribute="value"/> <read id="gaoyan-read-0" tag="primitive" ref="rsc0"/> <write id="gaoyan-write-1 ref="location_rsc0"/> <write id="gaoyan-write-2" tag="nodes"/> <write id="gaoyan-write-3" tag="status"/> </user> <user id="bob" role="operator"/> <user id="1002" role="monitor"/> </acls> .. As Andrew suggested: - Roles have ACLs - Users can be assigned EITHER a role OR a set of ACLs Besides, An user "id" could be a system username or a numeric uid. For crm shell, perhaps the syntax would be like: user <id> acl_obj [acl_obj ...] user <id> <role_id> role <id> acl_obj [acl_obj ...] acl_obj :: mode tag <tag_name> [attribute] mode ref <ref_id> [attribute] mode tag <tag_name> ref <ref_id> [attribute] mode xpath <path> [attribute] mode:: read | write | deny Attached the updated patch. Please help review it. Thanks! Regards, Yan -- Yan Gao <y...@novell.com> Software Engineer China Server Team, OPS Engineering, Novell, Inc.
# HG changeset patch # User Yan Gao <y...@novell.com> # Date 1265036078 -28800 # Node ID 7fecd951b21c6d820654e6115a4d96e839641cda # Parent ae2f826609edfedb37aeb4b997171f55bb509352 Dev: cib: Implement multi-level ACLs for the CIB diff -r ae2f826609ed -r 7fecd951b21c cib/callbacks.c --- a/cib/callbacks.c Mon Jan 25 10:54:01 2010 +0100 +++ b/cib/callbacks.c Mon Feb 01 22:54:38 2010 +0800 @@ -298,8 +298,16 @@ } } + if(cib_client->user == NULL) { + value = crm_element_value(op_request, F_CIB_USER); + if(value != NULL) { + cib_client->user = crm_strdup(value); + } + } + crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id); crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name); + crm_xml_add(op_request, F_CIB_USER, cib_client->user); /* crm_log_xml(LOG_MSG, "Client[inbound]", op_request); */ if(cib_client->callback_id == NULL) { @@ -797,6 +805,7 @@ xmlNode *output = NULL; xmlNode *result_cib = NULL; xmlNode *current_cib = NULL; + xmlNode *filtered_current_cib = NULL; int call_type = 0; int call_options = 0; @@ -838,9 +847,20 @@ goto done; } else if(cib_op_modifies(call_type) == FALSE) { - rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, + if (acl_filter_cib(request, current_cib, current_cib, &filtered_current_cib) == FALSE) { + rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, section, request, input, FALSE, &config_changed, current_cib, &result_cib, NULL, &output); + } else { + crm_debug("Pre-filtered the queried cib according to the ACLs"); + if (filtered_current_cib == NULL) { + rc = cib_permission_denied; + } else { + rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE, + section, request, input, FALSE, &config_changed, + filtered_current_cib, &result_cib, NULL, &output); + } + } CRM_CHECK(result_cib == NULL, free_xml(result_cib)); goto done; @@ -877,6 +897,10 @@ *cib_diff = diff_cib_object(current_cib, result_cib, FALSE); config_changed = cib_config_changed(*cib_diff); } + + if (acl_check_diff(request, current_cib, result_cib, *cib_diff) == FALSE) { + rc = cib_permission_denied; + } } if(rc == cib_ok) { @@ -901,11 +925,22 @@ } } else if(rc == cib_dtd_validation) { + xmlNode *filtered_result_cib = NULL; + if(output != NULL) { crm_log_xml_info(output, "cib:output"); free_xml(output); } - output = result_cib; + + if (acl_filter_cib(request, current_cib, result_cib, &filtered_result_cib) == FALSE) { + output = result_cib; + } else { + crm_debug("Filtered the result cib for output according to the ACLs"); + output = filtered_result_cib; + if (result_cib != NULL) { + free_xml(result_cib); + } + } } else { free_xml(result_cib); @@ -949,6 +984,10 @@ if((call_options & cib_discard_reply) == 0) { *reply = cib_construct_reply(request, output, rc); /* crm_log_xml_info(*reply, "cib:reply"); */ + } + + if (filtered_current_cib != NULL) { + free_xml(filtered_current_cib); } if(call_type >= 0) { diff -r ae2f826609ed -r 7fecd951b21c cib/callbacks.h --- a/cib/callbacks.h Mon Jan 25 10:54:01 2010 +0100 +++ b/cib/callbacks.h Mon Feb 01 22:54:38 2010 +0800 @@ -34,6 +34,7 @@ char *id; char *name; char *callback_id; + char *user; const char *channel_name; diff -r ae2f826609ed -r 7fecd951b21c cib/remote.c --- a/cib/remote.c Mon Jan 25 10:54:01 2010 +0100 +++ b/cib/remote.c Mon Feb 01 22:54:38 2010 +0800 @@ -289,6 +289,8 @@ CRM_CHECK(new_client->id == NULL, crm_free(new_client->id)); new_client->id = crm_strdup(uuid_str); + + new_client->user = crm_strdup(user); new_client->callback_id = NULL; if(ssock == remote_tls_fd) { diff -r ae2f826609ed -r 7fecd951b21c include/crm/cib.h --- a/include/crm/cib.h Mon Jan 25 10:54:01 2010 +0100 +++ b/include/crm/cib.h Mon Feb 01 22:54:38 2010 +0800 @@ -120,6 +120,7 @@ cib_bad_config = -51, cib_invalid_argument = -52, cib_transform_failed = -53, + cib_permission_denied = -54, }; enum cib_update_op { @@ -182,6 +183,7 @@ #define F_CIB_NOTIFY_TYPE "cib_notify_type" #define F_CIB_NOTIFY_ACTIVATE "cib_notify_activate" #define F_CIB_UPDATE_DIFF "cib_update_diff" +#define F_CIB_USER "cib_user" #define T_CIB "cib" #define T_CIB_NOTIFY "cib_notify" diff -r ae2f826609ed -r 7fecd951b21c include/crm/msg_xml.h --- a/include/crm/msg_xml.h Mon Jan 25 10:54:01 2010 +0100 +++ b/include/crm/msg_xml.h Mon Feb 01 22:54:38 2010 +0800 @@ -118,6 +118,7 @@ #define XML_CIB_TAG_CRMCONFIG "crm_config" #define XML_CIB_TAG_OPCONFIG "op_defaults" #define XML_CIB_TAG_RSCCONFIG "rsc_defaults" +#define XML_CIB_TAG_ACLS "acls" #define XML_CIB_TAG_STATE "node_state" #define XML_CIB_TAG_NODE "node" @@ -264,6 +265,17 @@ #define XML_TAG_DIFF_ADDED "diff-added" #define XML_TAG_DIFF_REMOVED "diff-removed" +#define XML_ACL_TAG_USER "user" +#define XML_ACL_TAG_ROLE "role" +#define XML_ACL_ATTR_ROLE "role" +#define XML_ACL_TAG_READ "read" +#define XML_ACL_TAG_WRITE "write" +#define XML_ACL_TAG_DENY "deny" +#define XML_ACL_ATTR_REF "ref" +#define XML_ACL_ATTR_TAG "tag" +#define XML_ACL_ATTR_XPATH "xpath" +#define XML_ACL_ATTR_ATTRIBUTE "attribute" + #include <crm/common/xml.h> #define ID(x) crm_element_value(x, XML_ATTR_ID) diff -r ae2f826609ed -r 7fecd951b21c lib/cib/Makefile.am --- a/lib/cib/Makefile.am Mon Jan 25 10:54:01 2010 +0100 +++ b/lib/cib/Makefile.am Mon Feb 01 22:54:38 2010 +0800 @@ -26,7 +26,7 @@ ## SOURCES noinst_HEADERS = cib_private.h libcib_la_SOURCES = cib_ops.c cib_utils.c cib_client.c cib_native.c cib_attrs.c \ - cib_version.c cib_file.c cib_remote.c + cib_version.c cib_file.c cib_remote.c cib_acl.c libcib_la_LDFLAGS = -version-info 1:1:0 $(top_builddir)/lib/common/libcrmcommon.la $(CRYPTOLIB) libcib_la_CFLAGS = -I$(top_srcdir) diff -r ae2f826609ed -r 7fecd951b21c lib/cib/cib_acl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/cib/cib_acl.c Mon Feb 01 22:54:38 2010 +0800 @@ -0,0 +1,727 @@ +/* + * Copyright (C) 2009 Yan Gao <y...@novell.com> + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This software 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 + * General Public License for more details. + * + * You should have received a copy of the GNU General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include <pwd.h> + +#include <crm/cib.h> +#include <cib_private.h> + +typedef struct acl_obj_s +{ + const char *mode; + const char *tag; + const char *ref; + const char *xpath; + const char *attribute; +} acl_obj_t; + +typedef struct xml_perm_s +{ + const char *mode; + GHashTable *attribute_perms; +} xml_perm_t; + +static gboolean req_by_superuser(xmlNode *request); + +static gboolean unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl); +static gboolean user_match(const char *user, const char *uid); +static gboolean unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl); +static gboolean unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl); +static gboolean acl_append(xmlNode *acl_child, GListPtr *acl); +static void free_acl(GListPtr acl); +static gboolean parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl); + +static gboolean gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms); +static int search_xml_children(GListPtr *children, xmlNode *root, + const char *tag, const char *field, const char *value, + gboolean search_matches); +static int search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath); +static gboolean update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms); +static gboolean update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms); +static void free_xml_perm(gpointer xml_perm); + +static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms); +static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms); + + +/* rc = TRUE if orig_cib has been filtered*/ +/* That means *filtered_cib rather than orig_cib should be exploited afterwards*/ +gboolean +acl_filter_cib(xmlNode *request, xmlNode *current_cib, xmlNode *orig_cib, xmlNode **filtered_cib) +{ + const char *user = NULL; + xmlNode *xml_acls = NULL; + xmlNode *tmp_cib = NULL; + GListPtr user_acl = NULL; + GHashTable *xml_perms = NULL; + + *filtered_cib = NULL; + + if (req_by_superuser(request)) { + return FALSE; + } + + if (orig_cib == NULL) { + return FALSE; + } + + if (current_cib == NULL) { + return TRUE; + } + + user = crm_element_value(request, F_CIB_USER); + + xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); + if (xml_acls == NULL) { + crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); + return TRUE; + } + + unpack_user_acl(xml_acls, user, &user_acl); + + tmp_cib = copy_xml(orig_cib); + + gen_xml_perms(tmp_cib, user_acl, &xml_perms); + + if (acl_filter_xml(tmp_cib, xml_perms)) { + crm_debug("User '%s' doesn't have the permission for the whole CIB", user); + tmp_cib = NULL; + } + + g_hash_table_destroy(xml_perms); + free_acl(user_acl); + + *filtered_cib = tmp_cib; + return TRUE; +} + +/* rc = TRUE if the request passes the ACL check */ +/* rc = FALSE if the permission is denied */ +gboolean +acl_check_diff(xmlNode *request, xmlNode *current_cib, xmlNode *result_cib, xmlNode *diff) +{ + const char *user = NULL; + xmlNode *xml_acls = NULL; + GListPtr user_acl = NULL; + int rc = FALSE; + + if (req_by_superuser(request)) { + return TRUE; + } + + if (diff == NULL) { + return TRUE; + } + + if (current_cib == NULL) { + return FALSE; + } + + user = crm_element_value(request, F_CIB_USER); + + xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); + if (xml_acls == NULL) { + crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); + return FALSE; + } + + unpack_user_acl(xml_acls, user, &user_acl); + + xml_child_iter( + diff, diff_child, + const char *tag = crm_element_name(diff_child); + GListPtr parsed_acl = NULL; + + crm_debug("Preparing ACL checking on '%s'", tag); + + if (crm_str_eq(tag, XML_TAG_DIFF_REMOVED, TRUE)) { + crm_debug("Parsing any xpaths under the ACL according to the current CIB"); + parse_acl_xpath(current_cib, user_acl, &parsed_acl); + } else if (crm_str_eq(tag, XML_TAG_DIFF_ADDED, TRUE)) { + crm_debug("Parsing any xpaths under the ACL according to the result CIB"); + parse_acl_xpath(result_cib, user_acl, &parsed_acl); + } else { + continue; + } + + xml_child_iter( + diff_child, diff_cib, + GHashTable *xml_perms = NULL; + + gen_xml_perms(diff_cib, parsed_acl, &xml_perms); + rc = acl_check_diff_xml(diff_cib, xml_perms); + g_hash_table_destroy(xml_perms); + + if (rc == FALSE) { + crm_warn("User '%s' doesn't have the permission to modify the CIB", user); + goto done; + } + ); + free_acl(parsed_acl); + ); + +done: + free_acl(user_acl); + return rc; +} + +static gboolean +req_by_superuser(xmlNode *request) +{ + const char *user = crm_element_value(request, F_CIB_USER); + if (user == NULL || crm_str_eq(user, CRM_DAEMON_USER, TRUE) + || crm_str_eq(user, "root", TRUE)) { + return TRUE; + } + return FALSE; +} + +static gboolean +unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl) +{ + if (xml_acls == NULL) { + return FALSE; + } + + xml_child_iter( + xml_acls, xml_acl, + const char *tag = crm_element_name(xml_acl); + const char *id = crm_element_value(xml_acl, XML_ATTR_ID); + + if (crm_str_eq(tag, XML_ACL_TAG_USER, TRUE)) { + if (user_match(user, id)) { + crm_debug("Unpacking ACL of user: '%s'", id); + unpack_acl(xml_acls, xml_acl, user_acl); + return TRUE; + } + } + ); + return FALSE; +} + +static gboolean +user_match(const char *user, const char *uid) +{ + struct passwd *pwent = NULL; + int user_uid_num = -1; + int uid_num = -1; + + if (crm_str_eq(user, uid, TRUE)) { + return TRUE; + } + + pwent = getpwnam(user); + if (pwent == NULL) { + crm_warn("No user named '%s' exists!", user); + return FALSE; + } + user_uid_num = pwent->pw_uid; + + uid_num = crm_int_helper(uid, NULL); + if(errno == 0 && uid_num == user_uid_num) { + return TRUE; + } + + return FALSE; +} + +static gboolean +unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl) +{ + const char *role = crm_element_value(xml_acl, XML_ACL_ATTR_ROLE); + + if (role) { + unpack_role_acl(xml_acls, role, acl); + } + + xml_child_iter( + xml_acl, acl_child, + const char *tag = crm_element_name(acl_child); + + if (crm_str_eq(XML_ACL_TAG_READ, tag, TRUE) + || crm_str_eq(XML_ACL_TAG_WRITE, tag, TRUE) + || crm_str_eq(XML_ACL_TAG_DENY, tag, TRUE)) + acl_append(acl_child, acl); + ); + return TRUE; +} + +static gboolean +unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl) +{ + xml_child_iter_filter( + xml_acls, xml_acl, XML_ACL_TAG_ROLE, + const char *role_id = crm_element_value(xml_acl, XML_ATTR_ID); + + if (role_id && crm_str_eq(role, role_id, TRUE)) { + crm_debug("Unpacking ACL of the referenced role: '%s'", role); + unpack_acl(xml_acls, xml_acl, acl); + return TRUE; + } + ); + return FALSE; +} + +static gboolean +acl_append(xmlNode *acl_child, GListPtr *acl) +{ + acl_obj_t *acl_obj = NULL; + + const char *tag = crm_element_value(acl_child, XML_ACL_ATTR_TAG); + const char *ref = crm_element_value(acl_child, XML_ACL_ATTR_REF); + const char *xpath = crm_element_value(acl_child, XML_ACL_ATTR_XPATH); + + if (tag == NULL && ref == NULL && xpath == NULL) { + return FALSE; + } + + crm_malloc0(acl_obj, sizeof(acl_obj_t)); + if (acl_obj == NULL) { + return FALSE; + } + + acl_obj->mode = crm_element_name(acl_child); + acl_obj->tag = tag; + acl_obj->ref = ref; + acl_obj->xpath = xpath; + acl_obj->attribute = crm_element_value(acl_child, XML_ACL_ATTR_ATTRIBUTE); + + *acl = g_list_append(*acl, acl_obj); + + crm_debug_3("ACL object appended: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", + acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); + + return TRUE; +} + +static void +free_acl(GListPtr acl) +{ + GListPtr iterator = acl; + while(iterator != NULL) { + crm_free(iterator->data); + iterator = iterator->next; + } + if(acl != NULL) { + g_list_free(acl); + } +} + +static gboolean +parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl) +{ + GListPtr acl_iterator = acl; + acl_obj_t *new_acl_obj = NULL; + + *parsed_acl = NULL; + + while (acl_iterator != NULL) { + acl_obj_t *acl_obj = acl_iterator->data; + + if (acl_obj->tag || acl_obj->ref) { + crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); + if (new_acl_obj == NULL) { + return FALSE; + } + + memcpy(new_acl_obj, acl_obj, sizeof(acl_obj_t)); + + *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); + + crm_debug_3("Copied ACL object: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", + new_acl_obj->mode, new_acl_obj->tag, new_acl_obj->ref, + new_acl_obj->xpath, new_acl_obj->attribute); + + } else if (acl_obj->xpath) { + GListPtr children = NULL; + GListPtr children_iterator = NULL; + + search_xpath_objects(&children, xml, acl_obj->xpath); + + children_iterator = children; + while (children_iterator != NULL) { + crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); + if (new_acl_obj == NULL) { + return FALSE; + } + + new_acl_obj->mode = acl_obj->mode; + new_acl_obj->tag = crm_element_name(children_iterator->data); + new_acl_obj->ref = crm_element_value(children_iterator->data, XML_ATTR_ID); + new_acl_obj->attribute = acl_obj->attribute; + + *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); + + crm_debug_3("Parsed the ACL object with xpath '%s' to: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", + acl_obj->xpath, new_acl_obj->mode, new_acl_obj->tag, + new_acl_obj->ref, new_acl_obj->xpath, new_acl_obj->attribute); + + children_iterator = children_iterator->next; + } + g_list_free(children); + } + acl_iterator = acl_iterator->next; + } + + return TRUE; +} + +static gboolean +gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms) +{ + GListPtr acl_iterator = acl; + + if (*xml_perms == NULL) { + *xml_perms = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_xml_perm); + } + + while (acl_iterator != NULL) { + acl_obj_t *acl_obj = acl_iterator->data; + GListPtr children = NULL; + GListPtr children_iterator = NULL; + + crm_debug("Generating permissions with ACL: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", + acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); + if (acl_obj->tag || acl_obj->ref) { + search_xml_children(&children, xml, acl_obj->tag, XML_ATTR_ID, acl_obj->ref, TRUE); + + } else if (acl_obj->xpath) { + /* Never be here for a modification operation */ + /* Already parse_acl_xpath() previously */ + search_xpath_objects(&children, xml, acl_obj->xpath); + } + + children_iterator = children; + while (children_iterator != NULL) { + update_xml_perms(children_iterator->data, acl_obj, *xml_perms); + + children_iterator = children_iterator->next; + } + g_list_free(children); + + acl_iterator = acl_iterator->next; + } + + return TRUE; +} + +/* Borrowed from lib/common/xml.c: find_xml_children() */ +/* But adding the original xmlNode pointers into a GList */ +static int +search_xml_children(GListPtr *children, xmlNode *root, + const char *tag, const char *field, const char *value, + gboolean search_matches) +{ + int match_found = 0; + + CRM_CHECK(root != NULL, return FALSE); + CRM_CHECK(children != NULL, return FALSE); + + if(tag != NULL && safe_str_neq(tag, crm_element_name(root))) { + + } else if(value != NULL + && safe_str_neq(value, crm_element_value(root, field))) { + + } else { + *children = g_list_append(*children, root); + match_found = 1; + } + + if(search_matches || match_found == 0) { + xml_child_iter( + root, child, + match_found += search_xml_children( + children, child, tag, field, value, + search_matches); + ); + } + + return match_found; +} + +static int +search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath) +{ + int match_found = 0; + xmlXPathObjectPtr xpathObj = NULL; + + if(xpath == NULL) { + return 0; + } + + xpathObj = xpath_search(xml_obj, xpath); + + if(xpathObj == NULL || xpathObj->nodesetval == NULL || xpathObj->nodesetval->nodeNr < 1) { + crm_debug("No match for %s in %s", xpath, xmlGetNodePath(xml_obj)); + + } else if(xpathObj->nodesetval->nodeNr > 0) { + int lpc = 0, max = xpathObj->nodesetval->nodeNr; + + for(lpc = 0; lpc < max; lpc++) { + xmlNode *match = getXpathResult(xpathObj, lpc); + if (match == NULL) { + continue; + } + + *objects = g_list_append(*objects, match); + match_found++; + } + } + + if(xpathObj) { + xmlXPathFreeObject(xpathObj); + } + return match_found; +} + +static gboolean +update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms) +{ + xml_perm_t *perm = NULL; + + if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { + if (perm->mode != NULL) { + return FALSE; + } + } else { + crm_malloc0(perm, sizeof(xml_perm_t)); + if (perm == NULL) { + return FALSE; + } + g_hash_table_insert(xml_perms, xml, perm); + } + + if (acl_obj->attribute == NULL) { + perm->mode = acl_obj->mode; + crm_debug_3("Permission for element: element_mode=%s, tag=%s, id=%s", + perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + + xml_child_iter( + xml, child, + update_xml_children_perms(child, perm->mode, xml_perms); + ); + + } else { + if (perm->attribute_perms == NULL + || (g_hash_table_lookup_extended(perm->attribute_perms, + acl_obj->attribute, NULL, NULL) == FALSE)) { + + if (perm->attribute_perms == NULL) { + perm->attribute_perms = g_hash_table_new_full( + g_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); + } + + g_hash_table_insert(perm->attribute_perms, + crm_strdup(acl_obj->attribute), crm_strdup(acl_obj->mode)); + crm_debug_3("Permission for attribute: attribute_mode=%s, tag=%s, id=%s attribute=%s", + acl_obj->mode, crm_element_name(xml), + crm_element_value(xml, XML_ATTR_ID), acl_obj->attribute); + } + } + + return TRUE; +} + +static gboolean +update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms) +{ + xml_perm_t *perm = NULL; + + if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { + if (perm->mode != NULL) { + return FALSE; + } + } else { + crm_malloc0(perm, sizeof(xml_perm_t)); + if (perm == NULL) { + return FALSE; + } + g_hash_table_insert(xml_perms, xml, perm); + } + + perm->mode = mode; + crm_debug_4("Permission for child element: element_mode=%s, tag=%s, id=%s", + mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + + xml_child_iter( + xml, child, + update_xml_children_perms(child, mode, xml_perms); + ); + + return TRUE; +} + +static void +free_xml_perm(gpointer xml_perm) +{ + xml_perm_t *perm = xml_perm; + + if (perm == NULL) { + return; + } + + if (perm->attribute_perms != NULL) { + g_hash_table_destroy(perm->attribute_perms); + } + + crm_free(perm); +} + +#define can_read(mode) (crm_str_eq(mode, XML_ACL_TAG_READ, TRUE) \ + || crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE)) + +#define can_write(mode) crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE) + +/* rc = TRUE if the xml is filtered out*/ +static gboolean +acl_filter_xml(xmlNode *xml, GHashTable *xml_perms) +{ + int children_counter = 0; + xml_perm_t *perm = NULL; + int allow_counter = 0; + + xml_child_iter( + xml, child, + if (acl_filter_xml(child, xml_perms) == FALSE) { + children_counter++; + } + ); + + g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); + + if (perm == NULL) { + crm_debug_4("No ACL defined to read the element: tag=%s, id=%s", + crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + goto end_filter; + } + + if (perm->attribute_perms == NULL) { + if (can_read(perm->mode)) { + return FALSE; + } else { + crm_debug_4("No enough permission to read the element: element_mode=%s, tag=%s, id=%s", + perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + goto end_filter; + } + } + + xml_prop_iter(xml, prop_name, prop_value, + gpointer mode = NULL; + + if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { + if (can_read(mode)) { + allow_counter++; + } else { + xml_remove_prop(xml, prop_name); + crm_debug_4("Filtered out the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", + (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + } + } else { + if (can_read(perm->mode)) { + allow_counter++; + } else if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE) == FALSE) { + xml_remove_prop(xml, prop_name); + crm_debug_4("Filtered out the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", + perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + } + } + ); + + if (allow_counter) { + return FALSE; + } + + if (can_read(perm->mode)) { + return FALSE; + } + +end_filter: + if (children_counter) { + crm_debug_4("Don't filter out the element (tag=%s, id=%s) because user can read its children", + crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + return FALSE; + } + + free_xml_from_parent(NULL, xml); + crm_debug_4("Filtered out the element: tag=%s, id=%s", + crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); + return TRUE; +} + +static gboolean +acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms) +{ + xml_perm_t *perm = NULL; + + xml_child_iter( + xml, child, + if (acl_check_diff_xml(child, xml_perms) == FALSE) { + return FALSE; + } + ); + + g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); + + xml_prop_iter(xml, prop_name, prop_value, + gpointer mode = NULL; + + if (crm_str_eq(crm_element_name(xml), XML_TAG_CIB, TRUE)) { + if (crm_str_eq(prop_name, XML_ATTR_GENERATION, TRUE) + || crm_str_eq(prop_name, XML_ATTR_NUMUPDATES, TRUE) + || crm_str_eq(prop_name, XML_ATTR_GENERATION_ADMIN, TRUE)) { + continue; + } + } + + if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE)) { + continue; + } + + if (perm == NULL) { + crm_debug("No ACL defined to modify the element: tag=%s, id=%s, attribute=%s", + crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + return FALSE; + } + + if (perm->attribute_perms == NULL) { + if (can_write(perm->mode)) { + return TRUE; + } else { + crm_debug("No enough permission to modify the element: element_mode=%s, tag=%s, id=%s, attribute=%s", + perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + return FALSE; + } + } + + if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { + if (can_write(mode) == FALSE) { + crm_debug("No enough permission to modify the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", + (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + return FALSE; + } + } else if (can_write(perm->mode) == FALSE) { + crm_debug("No enough permission to modify the element and the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", + perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); + return FALSE; + } + + ); + + return TRUE; +} + diff -r ae2f826609ed -r 7fecd951b21c lib/cib/cib_native.c --- a/lib/cib/cib_native.c Mon Jan 25 10:54:01 2010 +0100 +++ b/lib/cib/cib_native.c Mon Feb 01 22:54:38 2010 +0800 @@ -22,6 +22,7 @@ #include <stdio.h> #include <stdarg.h> #include <string.h> +#include <pwd.h> #include <glib.h> @@ -155,9 +156,19 @@ } if(rc == cib_ok) { + int uid = geteuid(); + struct passwd *pwent = NULL; + CRM_CHECK(native->token != NULL, ;); hello = cib_create_op(0, native->token, CRM_OP_REGISTER, NULL, NULL, NULL, 0); crm_xml_add(hello, F_CIB_CLIENTNAME, name); + + pwent = getpwuid(uid); + if (pwent == NULL) { + crm_perror(LOG_ERR, "Cannot get name for uid: %d", uid); + } else { + crm_xml_add(hello, F_CIB_USER, crm_strdup(pwent->pw_name)); + } if(send_ipc_message(native->command_channel, hello) == FALSE) { rc = cib_callback_register; diff -r ae2f826609ed -r 7fecd951b21c lib/cib/cib_private.h --- a/lib/cib/cib_private.h Mon Jan 25 10:54:01 2010 +0100 +++ b/lib/cib/cib_private.h Mon Feb 01 22:54:38 2010 +0800 @@ -71,5 +71,8 @@ cib_t *cib, int call_id, int timeout, gboolean only_success, void *user_data, const char *callback_name, void (*callback)(xmlNode*, int, int, xmlNode*,void*)); +extern gboolean acl_filter_cib(xmlNode *request, xmlNode *current_cib, xmlNode *orig_cib, xmlNode **filtered_cib); +extern gboolean acl_check_diff(xmlNode *request, xmlNode *current_cib, xmlNode *result_cib, xmlNode *diff); + #endif diff -r ae2f826609ed -r 7fecd951b21c lib/cib/cib_utils.c --- a/lib/cib/cib_utils.c Mon Jan 25 10:54:01 2010 +0100 +++ b/lib/cib/cib_utils.c Mon Feb 01 22:54:38 2010 +0800 @@ -56,6 +56,7 @@ { XML_CIB_TAG_CONSTRAINTS, "/cib/configuration", "//cib/configuration/constraints" }, { XML_CIB_TAG_OPCONFIG, "/cib/configuration", "//cib/configuration/op_defaults" }, { XML_CIB_TAG_RSCCONFIG, "/cib/configuration", "//cib/configuration/rsc_defaults" }, + { XML_CIB_TAG_ACLS, "/cib/configuration", "//cib/configuration/acls" }, { XML_CIB_TAG_SECTION_ALL, NULL, "//cib" }, }; @@ -225,6 +226,9 @@ break; case cib_transform_failed: error_msg = "Schema transform failed"; + break; + case cib_permission_denied: + error_msg = "Permission Denied"; break; } diff -r ae2f826609ed -r 7fecd951b21c xml/pacemaker.rng.in --- a/xml/pacemaker.rng.in Mon Jan 25 10:54:01 2010 +0100 +++ b/xml/pacemaker.rng.in Mon Feb 01 22:54:38 2010 +0800 @@ -44,6 +44,9 @@ <element name="constraints"> <externalRef href="constrain...@crm_dtd_version@.rng"/> </element> + <optional> + <ref name="element-acls"/> + </optional> </interleave> </element> <element name="status"> @@ -134,4 +137,58 @@ </zeroOrMore> </define> + <define name="element-acls"> + <element name="acls"> + <zeroOrMore> + <choice> + <element name="user"> + <attribute name="id"><text/></attribute> + <choice> + <attribute name="role"><data type="IDREF"/></attribute> + <zeroOrMore> + <ref name="element-acl"/> + </zeroOrMore> + </choice> + </element> + <element name="role"> + <attribute name="id"><data type="ID"/></attribute> + <zeroOrMore> + <ref name="element-acl"/> + </zeroOrMore> + </element> + </choice> + </zeroOrMore> + </element> + </define> + + <define name="element-acl"> + <choice> + <element name="read"> + <ref name="attribute-acl"/> + </element> + <element name="write"> + <ref name="attribute-acl"/> + </element> + <element name="deny"> + <ref name="attribute-acl"/> + </element> + </choice> + </define> + + <define name="attribute-acl"> + <attribute name="id"><data type="ID"/></attribute> + <choice> + <attribute name="tag"><text/></attribute> + <attribute name="ref"><data type="IDREF"/></attribute> + <group> + <attribute name="tag"><text/></attribute> + <attribute name="ref"><data type="IDREF"/></attribute> + </group> + <attribute name="xpath"><text/></attribute> + </choice> + <optional> + <attribute name="attribute"><text/></attribute> + </optional> + </define> + </grammar>
_______________________________________________ Pacemaker mailing list Pacemaker@oss.clusterlabs.org http://oss.clusterlabs.org/mailman/listinfo/pacemaker