Hi Andrew, On 02/23/10 17:23, Yan Gao wrote: > On 02/23/10 04:10, Andrew Beekhof wrote: >> On Mon, Feb 22, 2010 at 8:58 AM, Yan Gao <y...@novell.com> wrote: >>> Hi Andrew, >>> >>> On 02/08/10 17:48, Andrew Beekhof wrote: >>>> On Thu, Feb 4, 2010 at 5:24 PM, Yan Gao <y...@novell.com> wrote: >>>>>> And put exclusions for things like passwords before the read for the >>>>>> whole cib? >>>>> Yes. We should specify any "deny" and "write" objects before it. >>>> >>>> I like the syntax now, but my original concern (that all the >>>> validation occurs in the client library) remains... so this still >>>> isn't providing any real security. >>> Right. If it's impossible for cib to run as root, >> >> If you need root for this, I think we can allow that change for 1.1. >> > Great! So PAM is still preferred. Anyway, I'll have a dig at different > ways. I think we can make that change when the authentication is ready, > and if it's necessary. After investigating, I found that Unix domain sockets provide methods to identify the user on the other side of a socket. That means we don't need PAM to do authentication for local access, and the clients doesn't need to prompt user to input and transfer username/password to the server. And cib daemon still can run as "hacluster".
I've improved the ipcsocket library of cluster-glue to record user's identity info for cib to use. The behavior of remote access to the cib is still like before. Attached the patch for cluster-glue and the updated patch for pacemaker. Looking forward to your review and comments. Thanks! BTW, a little revision of devel branch: diff -r f78972892449 configure.ac --- a/configure.ac Wed Mar 17 16:03:23 2010 +0800 +++ b/configure.ac Wed Mar 17 16:19:06 2010 +0800 @@ -431,7 +431,7 @@ dnl Create symlinks to here from CRM_DAEMON_DIR when needed HB_DAEMON_DIR=`extract_header_define $GLUE_HEADER HA_LIBHBDIR` -AC_DEFINE_UNQUOTED(HB_DAEMON_DIR,"HB_DAEMON_DIR", Location for Heartbeat expects Pacemaker daemons to be in) +AC_DEFINE_UNQUOTED(HB_DAEMON_DIR,"$HB_DAEMON_DIR", Location for Heartbeat expects Pacemaker daemons to be in) AC_SUBST(HB_DAEMON_DIR) dnl Needed so that the AIS plugin can clear out the directory as Heartbeat does 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 1268813003 -28800 # Node ID f78972892449bdd7d26f7404ea09b5c4db8cca29 # Parent 628b184a9047f47bfe2b50be157a1566ab03d9ad Dev: cib: Implement multi-level ACLs for the CIB diff -r 628b184a9047 -r f78972892449 cib/callbacks.c --- a/cib/callbacks.c Thu Mar 11 10:51:18 2010 +0100 +++ b/cib/callbacks.c Wed Mar 17 16:03:23 2010 +0800 @@ -26,6 +26,7 @@ #include <stdlib.h> #include <errno.h> #include <fcntl.h> +#include <pwd.h> #include <crm/crm.h> #include <crm/cib.h> @@ -298,8 +299,20 @@ } } + if(cib_client->user == NULL) { + struct passwd *pwent = NULL; + + pwent = getpwuid(channel->farside_uid); + if (pwent == NULL) { + crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", channel->farside_uid); + } else { + cib_client->user = crm_strdup(pwent->pw_name); + } + } + 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) { @@ -794,6 +807,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; @@ -835,9 +849,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; @@ -874,6 +899,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) { @@ -898,11 +927,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); @@ -948,6 +988,10 @@ /* crm_log_xml_info(*reply, "cib:reply"); */ } + if (filtered_current_cib != NULL) { + free_xml(filtered_current_cib); + } + if(call_type >= 0) { cib_op_cleanup(call_type, call_options, &input, &output); } diff -r 628b184a9047 -r f78972892449 cib/callbacks.h --- a/cib/callbacks.h Thu Mar 11 10:51:18 2010 +0100 +++ b/cib/callbacks.h Wed Mar 17 16:03:23 2010 +0800 @@ -34,6 +34,7 @@ char *id; char *name; char *callback_id; + char *user; const char *channel_name; diff -r 628b184a9047 -r f78972892449 cib/remote.c --- a/cib/remote.c Thu Mar 11 10:51:18 2010 +0100 +++ b/cib/remote.c Wed Mar 17 16:03:23 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) { @@ -377,6 +379,7 @@ crm_xml_add(command, F_TYPE, T_CIB); crm_xml_add(command, F_CIB_CLIENTID, client->id); crm_xml_add(command, F_CIB_CLIENTNAME, client->name); + crm_xml_add(command, F_CIB_USER, client->user); if(crm_element_value(command, F_CIB_CALLID) == NULL) { cl_uuid_t call_id; diff -r 628b184a9047 -r f78972892449 include/crm/cib.h --- a/include/crm/cib.h Thu Mar 11 10:51:18 2010 +0100 +++ b/include/crm/cib.h Wed Mar 17 16:03:23 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 628b184a9047 -r f78972892449 include/crm/msg_xml.h --- a/include/crm/msg_xml.h Thu Mar 11 10:51:18 2010 +0100 +++ b/include/crm/msg_xml.h Wed Mar 17 16:03:23 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 628b184a9047 -r f78972892449 lib/cib/Makefile.am --- a/lib/cib/Makefile.am Thu Mar 11 10:51:18 2010 +0100 +++ b/lib/cib/Makefile.am Wed Mar 17 16:03:23 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 628b184a9047 -r f78972892449 lib/cib/cib_acl.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/lib/cib/cib_acl.c Wed Mar 17 16:03:23 2010 +0800 @@ -0,0 +1,735 @@ +/* + * 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); + if (user == NULL) { + crm_warn("Cannot identify the client user"); + return TRUE; + } + + 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); + if (user == NULL) { + crm_warn("Cannot identify the client user"); + return FALSE; + } + + 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 (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 628b184a9047 -r f78972892449 lib/cib/cib_private.h --- a/lib/cib/cib_private.h Thu Mar 11 10:51:18 2010 +0100 +++ b/lib/cib/cib_private.h Wed Mar 17 16:03:23 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 628b184a9047 -r f78972892449 lib/cib/cib_utils.c --- a/lib/cib/cib_utils.c Thu Mar 11 10:51:18 2010 +0100 +++ b/lib/cib/cib_utils.c Wed Mar 17 16:03:23 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" }, }; @@ -226,6 +227,9 @@ case cib_transform_failed: error_msg = "Schema transform failed"; break; + case cib_permission_denied: + error_msg = "Permission Denied"; + break; } if(error_msg == NULL) { diff -r 628b184a9047 -r f78972892449 xml/pacemaker-1.2.rng --- a/xml/pacemaker-1.2.rng Thu Mar 11 10:51:18 2010 +0100 +++ b/xml/pacemaker-1.2.rng Wed Mar 17 16:03:23 2010 +0800 @@ -44,6 +44,9 @@ <element name="constraints"> <externalRef href="constraints-1.2.rng"/> </element> + <optional> + <ref name="element-acls"/> + </optional> </interleave> </element> <element name="status"> @@ -123,4 +126,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>
# HG changeset patch # User Yan Gao <y...@novell.com> # Date 1268809568 -28800 # Node ID 699b8e950cdfbf8bd3a0fea9a991474ada5c5970 # Parent 5e7284501da699c53b0154ec7169e61a178a3c3b Dev: clplumbing: Add identity info of the user on the other side of socket diff -r 5e7284501da6 -r 699b8e950cdf include/clplumbing/ipc.h --- a/include/clplumbing/ipc.h Mon Mar 15 16:03:30 2010 +0100 +++ b/include/clplumbing/ipc.h Wed Mar 17 15:06:08 2010 +0800 @@ -132,6 +132,8 @@ int ch_status; /* identify the status of channel.*/ int refcount; /* reference count */ pid_t farside_pid; /* far side pid */ + uid_t farside_uid; /* far side uid */ + gid_t farside_gid; /* far side gid */ void* ch_private; /* channel private data. */ /* (may contain conn. info.) */ IPC_Ops* ops; /* IPC_Channel function table.*/ diff -r 5e7284501da6 -r 699b8e950cdf lib/clplumbing/ipcsocket.c --- a/lib/clplumbing/ipcsocket.c Mon Mar 15 16:03:30 2010 +0100 +++ b/lib/clplumbing/ipcsocket.c Wed Mar 17 15:06:08 2010 +0800 @@ -2204,6 +2204,8 @@ temp_ch->low_flow_mark = -1; temp_ch->conntype = conntype; temp_ch->refcount = 0; + temp_ch->farside_uid = -1; + temp_ch->farside_gid = -1; return temp_ch; @@ -2341,14 +2343,20 @@ } if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { - return IPC_OK; /* no restriction for authentication */ + ret = IPC_OK; /* no restriction for authentication */ } /* Get the credential information for our peer */ conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; if (getsockopt(conn_info->s, SOL_SOCKET, SO_PEERCRED, &cred, &n) != 0 || (size_t)n != sizeof(cred)) { - return IPC_FAIL; + return ret; + } + + ch->farside_uid = cred.uid; + ch->farside_gid = cred.gid; + if (ret == IPC_OK) { + return ret; } #if 0 cl_log(LOG_DEBUG, "SO_PEERCRED returned [%d, (%ld:%ld)]" @@ -2419,13 +2427,19 @@ if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { - return IPC_OK; /* no restriction for authentication */ + ret = IPC_OK; /* no restriction for authentication */ } conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; if (getpeereid(conn_info->s, &euid, &egid) < 0) { cl_perror("getpeereid() failure"); - return IPC_FAIL; + return ret; + } + + ch->farside_uid = euid; + ch->farside_gid = egid; + if (ret == IPC_OK) { + return ret; } /* Check credentials against authorization information */ @@ -2524,7 +2538,7 @@ #endif struct SOCKET_CH_PRIVATE *conn_info; - int ret = IPC_OK; + int ret = IPC_FAIL; char buf; /* Compute size without padding */ @@ -2543,7 +2557,7 @@ if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { - return IPC_OK; /* no restriction for authentication */ + ret = IPC_OK; /* no restriction for authentication */ } conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; @@ -2566,12 +2580,19 @@ || cmsg->cmsg_len < CMSGSIZE || cmsg->cmsg_type != SCM_CREDS) { cl_perror("can't get credential information from peer"); - return IPC_FAIL; + return ret; } /* Avoid alignment issues - just copy it! */ memcpy(&cred, CMSG_DATA(cmsg), sizeof(cred)); + ch->farside_uid = cred.crEuid; + ch->farside_gid = cred.crEgid; + if (ret == IPC_OK) { + return ret; + } + + ret = IPC_OK; if ( auth_info->uid && g_hash_table_lookup(auth_info->uid, &(cred.crEuid)) == NULL) { @@ -2618,7 +2639,7 @@ socket_verify_auth(struct IPC_CHANNEL* ch, struct IPC_AUTH * auth_info) { int len = 0; - int ret = IPC_OK; + int ret = IPC_FAIL; struct stat stat_buf; struct sockaddr_un *peer_addr = NULL; struct SOCKET_CH_PRIVATE *ch_private = NULL; @@ -2636,26 +2657,36 @@ } else if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { - return IPC_OK; /* no restriction for authentication */ + ret = IPC_OK; /* no restriction for authentication */ - } else if(ch_private == NULL) { + } + + if(ch_private == NULL) { cl_log(LOG_ERR, "No channel private data available"); - return IPC_FAIL; + return ret; } else if(peer_addr == NULL) { cl_log(LOG_ERR, "No peer information available"); - return IPC_FAIL; + return ret; } len = SUN_LEN(peer_addr); if(len < 1) { cl_log(LOG_ERR, "No peer information available"); - return IPC_FAIL; + return ret; } peer_addr->sun_path[len] = 0; stat(peer_addr->sun_path, &stat_buf); + ch->farside_uid = stat_buf.st_uid; + ch->farside_gid = stat_buf.st_gid; + if (ret == IPC_OK) { + return ret; + } + + ret = IPC_OK; + if ((auth_info->uid == NULL || g_hash_table_size(auth_info->uid) == 0) && auth_info->gid != NULL && g_hash_table_size(auth_info->gid) != 0) { @@ -2704,6 +2735,9 @@ conn_info = (struct SOCKET_CH_PRIVATE *) ch->ch_private; + ch->farside_uid = conn_info->farside_uid; + ch->farside_gid = conn_info->farside_gid; + if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { return IPC_OK; /* no restriction for authentication */ @@ -2751,12 +2785,18 @@ if (auth_info == NULL || (auth_info->uid == NULL && auth_info->gid == NULL)) { - return IPC_OK; /* no restriction for authentication */ + rc = IPC_OK; /* no restriction for authentication */ } if (getpeerucred(conn_info->s, &ucred) < 0) { cl_perror("getpeereid() failure"); - return IPC_FAIL; + return rc; + } + + ch->farside_uid = ucred_geteuid(ucred); + ch->farside_gid = ucred_getegid(ucred); + if (rc == IPC_OK) { + return rc; } /* Check credentials against authorization information */
_______________________________________________ Pacemaker mailing list Pacemaker@oss.clusterlabs.org http://oss.clusterlabs.org/mailman/listinfo/pacemaker