Hi all, Andrew, Lars,

Here's the status update about this feature.

I've implemented the main functionalities of ACL, including the ACLs
configuration parser, the CIB output filter and the modification checker...

Yan Gao wrote:
> On 12/09/09 18:28, Andrew Beekhof wrote:
>> On Wed, Dec 9, 2009 at 11:00 AM, Yan Gao <y...@novell.com> wrote:
>>> Hi Andrew, Lars,
>>>
>>> On 12/08/09 21:16, Lars Marowsky-Bree wrote:
>>>> On 2009-12-08T09:22:52, Andrew Beekhof <and...@beekhof.net> wrote:
>>>>
>>>>>> Basically, we'd like to see an ACL mechanism. It would be implemented at
>>>>>> the CIB level. So that all the clients - CLI , CRM shell, GUI, etc... -
>>>>>> could benefit. Clients are authenticated via PAM, so we can use uid/gid
>>>>>> for identification.
>>>>> Actually you probably can't do this.
>>>>> Daemons (like the cib) which are not running as root can only
>>>>> authenticate the username/password of the user they're running as.
>>>> Well, the non-root internal uids/daemons would of course get exceptions
>>>> just like root, this is about external interfaces.
>>> Actually, after thinking over the problem, I'm a bit confused...So I
>>> briefly describe what in my mind, please correct me if there's any problem.
>>>
>>> First, currently non-root users are able to connect the cib through
>>> either unix or network sockets as long as they belong to "haclient"
>>> group. We could keep this requirement.
>>>
>>> Then the cib should authenticate the client via PAM to identify who is
>>> connecting to it.
>> Thats what I'm saying, it can only do this for the hacluster user.
>> Because its not running as root.
> Indeed, that's the real problem. Without authentication, that would not
> be a real access control. No idea if there's any other solution... Lars,
> what's your recommendation?

For this authentication issue of local access we discussed last time, I
added a geteuid() in the cib_native_signon_raw() function from libcib.
Once a client signs on the CIB, it'll invoke the function and transfer
its uid to the server end.

Strictly speaking, an user could hack his client program or libcib to
obtain the privileged right to CIB. But he also could hack the server
end, right? ;-)

Above all, any user still needs to be added into "haclient" group by the
privileged user to get the access to CIB. That means at least the ACL
implementation would not lower the security of the current stuff.

Besides, for remote access, an user still needs to pass the PAM
authentication, to get his appropriate permission. Although for now it
only supports "hacluster" user.

The ACL checks would always be bypassed for privileged users, "root" and
"hacluster" .

The following is an example configuration to demonstrate how to use this
feature:

..
    <resources>
      <primitive class="ocf" id="rsc0" provider="pacemaker" type="Dummy">
        <instance_attributes id="rsc0-instance_attributes">
          <nvpair id="rsc0-instance_attributes-password" name="password" 
value="123"/>
        </instance_attributes>
        <meta_attributes id="rsc0-meta_attributes">
          <nvpair id="rsc0-meta_attributes-target-role" name="target-role" 
value="started"/>
        </meta_attributes>
      </primitive>
    </resources>
    <constraints/>
    <acls>
      <role id="admin">
        <write id="admin-write-0" tag="configuration"/>
        <write id="admin-write-1" tag="status"/>
      </role>
      <role id="operator">
        <write id="operator-write-0" tag="nodes"/>
        <write id="operator-write-1" tag="status"/>
      </role>
      <role id="monitor">
        <read id="operator-read-0" tag="nodes"/>
        <read id="monitor-read-1" tag="status"/>
        <members>
          <uid id="ygao"/>
        </members>
      </role>
      <user id="ygao">
        <write id="ygao-write-0" ref="rsc0-meta_attributes-target-role"/>
        <deny id="gaoyan-deny-0" ref="rsc0-instance_attributes-password"/>
        <read id="ygao-read-0" ref="rsc0"/>
        <role_ref id="operator"/>
      </user>
    </acls>
..

The user "ygao" is a system account.
We could define several roles as we wish, such as "admin", "operator" and 
"monitor",
which could contain a member list respectively if more than one user have the 
same
permissions. A role also could be referenced by a particular "<user ...>" 
definition.

As mentioned , the ACL is a black-/whitelist, and the first match defines
whether access is granted or denied.

So for user "ygao", the configuration means:

1. "ygao" has the write access to the "target-role" nvpair of "rsc0", hence
he could start/stop the resource.

Note: A "<write..>" ACL object also implies to grant read access.

2. "ygao" could read any other definitions of "rsc0" except "password" nvpair.

3. "ygao" can _read_ "nodes" and "status" sections.

Why is _read_ rather than _write_ ? We already referenced the "operator"
role who has write access, didn't we?
But we also put "ygao" into the members of "monitor" role, which is defined
prior to "<user id = "ygao"...". So after unpacking for "ygao", the ACL for him 
actually
is like:

<read id="operator-read-0" tag="nodes"/>
<read id="monitor-read-1" tag="status"/>
<write id="ygao-write-0" ref="rsc0-meta_attributes-target-role"/>
<deny id="gaoyan-deny-0" ref="rsc0-instance_attributes-password"/>
<read id="ygao-read-0" ref="rsc0"/>
<write id="operator-write-0" tag="nodes"/>
<write id="operator-write-1" tag="status"/>

Please always remember the basic rule:
"The first match determines the permission"

4. Everything else would be denied.


Those ACL objects, including "<read .../>", "<write .../>" and
"<deny .../>" could be interleaved with "<role_ref>" in definition.


Consider we have the definition:
<write id=... ref="rsc0"/>

This means the user could write any XML element who's ID is "rsc0".
So this applies to both the "primitive" and the "lrm_resource".

If we mean to grant the access only to the "primitive" one, we should
specify like:
<write id=... ref="rsc0" tag="primitive"/>

BTW, when you test it, please notice that the following command:
/usr/sbin/crm_resource -C -r rsc0

is not equal to:
/usr/sbin/cibadmin -D --xpath "//lrm_resour...@id='rsc0']"

The first command is achieved by requesting lrmd, which is running as root,
to do the cleanup for the client. So it could always bypass the ACL check.


BTW, there're some changes comparing to the original design:

For any of those ACL objects, including "<read .../>", "<write .../>" and
"<deny .../>", we need to specify an "id" for it, which it not in the original
design. I added it because some of the CIB modifications depend on element's 
IDs,
such as "cibadmin --modify".


There could be an "attribute" for an ACL object in the original design :
<write id=... ref="rsc0-meta_attributes-target-role" attribute="value" />

it was supposed to mean user could only write the "value" attribute of
"rsc0-meta_attributes-target-role" element.

I didn't implement it because there's no good way for now for the ACL
checker to recognize if a modification would change/add/remove any
particular attributes of a XML element. And I'm thinking if it's
necessary to implement it... Your thoughts?

Attached the patch. Please help review it. Any comments or suggestions
are welcome!

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 1263188859 -28800
# Node ID 0212ad85975b9431c80e00dce052471f8e2c77e5
# Parent  86a1d0023036929cd024f72eb9b58400c558f132
Dev: cib: Implement multi-level ACLs for the CIB

diff -r 86a1d0023036 -r 0212ad85975b cib/callbacks.c
--- a/cib/callbacks.c	Fri Jan 08 15:43:11 2010 +0100
+++ b/cib/callbacks.c	Mon Jan 11 13:47:39 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,19 @@
 	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 {
+	    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 +896,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, *cib_diff) == FALSE) {
+	    rc = cib_permission_denied;
+	}
     }    
     
     if(rc == cib_ok) {
@@ -901,11 +924,21 @@
 	}
 
     } 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 {
+	    output = filtered_result_cib;
+	    if (result_cib != NULL) {
+		free_xml(result_cib);
+	    }
+	}
 
     } else {
 	free_xml(result_cib);    
@@ -949,6 +982,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 86a1d0023036 -r 0212ad85975b cib/callbacks.h
--- a/cib/callbacks.h	Fri Jan 08 15:43:11 2010 +0100
+++ b/cib/callbacks.h	Mon Jan 11 13:47:39 2010 +0800
@@ -34,6 +34,7 @@
 		char  *id;
 		char  *name;
 		char  *callback_id;
+		char  *user;
 
 		const char  *channel_name;
 
diff -r 86a1d0023036 -r 0212ad85975b cib/remote.c
--- a/cib/remote.c	Fri Jan 08 15:43:11 2010 +0100
+++ b/cib/remote.c	Mon Jan 11 13:47:39 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 86a1d0023036 -r 0212ad85975b include/crm/cib.h
--- a/include/crm/cib.h	Fri Jan 08 15:43:11 2010 +0100
+++ b/include/crm/cib.h	Mon Jan 11 13:47:39 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 86a1d0023036 -r 0212ad85975b include/crm/msg_xml.h
--- a/include/crm/msg_xml.h	Fri Jan 08 15:43:11 2010 +0100
+++ b/include/crm/msg_xml.h	Mon Jan 11 13:47:39 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_TAG_ROLE_REF 		"role_ref"
+#define XML_ACL_TAG_MEMBERS		"members"
+#define XML_ACL_TAG_UID 		"uid"
+#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"
+
 #include <crm/common/xml.h> 
 
 #define ID(x) crm_element_value(x, XML_ATTR_ID)
diff -r 86a1d0023036 -r 0212ad85975b lib/cib/Makefile.am
--- a/lib/cib/Makefile.am	Fri Jan 08 15:43:11 2010 +0100
+++ b/lib/cib/Makefile.am	Mon Jan 11 13:47:39 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 86a1d0023036 -r 0212ad85975b lib/cib/cib_acl.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/lib/cib/cib_acl.c	Mon Jan 11 13:47:39 2010 +0800
@@ -0,0 +1,431 @@
+/* 
+ * 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>
+
+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 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 gboolean update_xml_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms);
+
+static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms);
+static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms);
+
+typedef struct acl_obj_s
+{
+	const char *mode;
+	const char *ref;
+	const char *tag;
+} acl_obj_t;
+
+/* 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;
+	}
+
+	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");
+		return TRUE;
+	}
+
+	user = crm_element_value(request, F_CIB_USER);
+	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_warn("Ordinary 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 *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;
+	}
+
+	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");
+		return FALSE;
+	}
+
+	user = crm_element_value(request, F_CIB_USER);
+	unpack_user_acl(xml_acls, user, &user_acl);
+
+	xml_child_iter(
+		diff, diff_child,
+		const char *tag = crm_element_name(diff_child);
+		if (crm_str_eq(tag, XML_TAG_DIFF_REMOVED, TRUE)
+				|| crm_str_eq(tag, XML_TAG_DIFF_ADDED, TRUE)) {
+			xml_child_iter(
+				diff_child, diff_cib,
+				GHashTable *xml_perms = NULL;
+
+				gen_xml_perms(diff_cib, user_acl, &xml_perms);
+				rc = acl_check_diff_xml(diff_cib, xml_perms);
+				g_hash_table_destroy(xml_perms);
+
+				if (rc == FALSE) {
+					crm_warn("Ordinary user \"%s\" doesn't have the permission", user);
+					goto done;
+				}
+				);
+		}
+		);
+
+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)) {
+				unpack_acl(xml_acls, xml_acl, user_acl);
+			}
+		} else if (crm_str_eq(tag, XML_ACL_TAG_ROLE, TRUE)) {
+			xml_child_iter_filter(
+				xml_acl, role_members, XML_ACL_TAG_MEMBERS,
+				xml_child_iter_filter(
+					role_members, role_member, XML_ACL_TAG_UID,
+					const char *role_user_uid = crm_element_value(role_member, XML_ATTR_ID);
+					if (user_match(user, role_user_uid)) {
+						unpack_acl(xml_acls, xml_acl, user_acl);
+					}
+					);
+				);
+		}
+		);
+	return TRUE;
+}
+
+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)
+{
+	xml_child_iter(
+		xml_acl, acl_child,
+		const char *tag = crm_element_name(acl_child);
+
+		if (crm_str_eq(XML_ACL_TAG_ROLE_REF, tag, TRUE)) {
+			const char *ref_role = crm_element_value(acl_child, XML_ATTR_ID);
+			if (ref_role) {
+				unpack_role_acl(xml_acls, ref_role, acl);
+			}
+		} else 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)) {
+			unpack_acl(xml_acls, xml_acl, acl);
+		}
+		);
+	return TRUE;
+}
+
+static gboolean
+acl_append(xmlNode *acl_child, GListPtr *acl)
+{
+	acl_obj_t *acl_obj = NULL;
+
+	const char *ref = crm_element_value(acl_child, XML_ACL_ATTR_REF);
+	const char *tag = crm_element_value(acl_child, XML_ACL_ATTR_TAG);
+
+	if (ref == NULL && tag == 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->ref = ref;
+	acl_obj->tag = tag;
+
+	*acl = g_list_append(*acl, acl_obj);
+
+	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
+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, g_hash_destroy_str);
+	}
+
+	while (acl_iterator != NULL) {
+		acl_obj_t *acl_obj = acl_iterator->data;
+		GListPtr children = NULL;
+		GListPtr children_iterator = NULL;
+
+		crm_debug_2("ACL: mode=%s, ref=%s, tag=%s", acl_obj->mode, acl_obj->ref, acl_obj->tag);
+		search_xml_children(&children, xml, acl_obj->tag, XML_ATTR_ID, acl_obj->ref, TRUE);
+
+		children_iterator = children;
+		while (children_iterator != NULL) {
+			update_xml_perms(children_iterator->data, acl_obj->mode, *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 gboolean
+update_xml_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms)
+{
+	if (g_hash_table_lookup_extended(xml_perms, xml, NULL, NULL)) {
+		return FALSE;
+	}
+
+	g_hash_table_insert(xml_perms, xml, crm_strdup(mode));
+	xml_child_iter(
+		xml, child,
+		update_xml_perms(child, mode, xml_perms);
+		);
+
+	return TRUE;
+}
+
+/* rc = TRUE if the xml is filtered out*/
+static gboolean
+acl_filter_xml(xmlNode *xml, GHashTable *xml_perms)
+{
+	int counter = 0;
+	gpointer value = NULL;
+
+	xml_child_iter(
+		xml, child, 
+		if (acl_filter_xml(child, xml_perms) == FALSE) {
+			counter += 1;
+		}
+		);
+
+	if (counter) {
+		return FALSE;
+	}
+
+	if (g_hash_table_lookup_extended(xml_perms, xml, NULL, &value)) {
+		if (crm_str_eq(value, XML_ACL_TAG_READ, TRUE)
+			|| crm_str_eq(value, XML_ACL_TAG_WRITE, TRUE)) {
+			return FALSE;
+		}
+	}
+
+	free_xml_from_parent(NULL, xml);
+	return TRUE;
+}
+
+static gboolean
+acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms)
+{
+	gpointer value = NULL;
+
+	xml_child_iter(
+		xml, child, 
+		if (acl_check_diff_xml(child, xml_perms) == FALSE) {
+			return FALSE;
+		}
+		);
+
+	if (crm_element_value(xml, XML_DIFF_MARKER) == NULL) {
+		return TRUE;
+	}
+
+	if (g_hash_table_lookup_extended(xml_perms, xml, NULL, &value)) {
+		if (crm_str_eq(value, XML_ACL_TAG_WRITE, TRUE)) {
+			return TRUE;
+		}
+	}
+
+	return FALSE;
+}
diff -r 86a1d0023036 -r 0212ad85975b lib/cib/cib_native.c
--- a/lib/cib/cib_native.c	Fri Jan 08 15:43:11 2010 +0100
+++ b/lib/cib/cib_native.c	Mon Jan 11 13:47:39 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 86a1d0023036 -r 0212ad85975b lib/cib/cib_private.h
--- a/lib/cib/cib_private.h	Fri Jan 08 15:43:11 2010 +0100
+++ b/lib/cib/cib_private.h	Mon Jan 11 13:47:39 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 *diff);
+
 
 #endif
diff -r 86a1d0023036 -r 0212ad85975b lib/cib/cib_utils.c
--- a/lib/cib/cib_utils.c	Fri Jan 08 15:43:11 2010 +0100
+++ b/lib/cib/cib_utils.c	Mon Jan 11 13:47:39 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 86a1d0023036 -r 0212ad85975b lib/common/xml.c
--- a/lib/common/xml.c	Fri Jan 08 15:43:11 2010 +0100
+++ b/lib/common/xml.c	Mon Jan 11 13:47:39 2010 +0800
@@ -1615,6 +1615,7 @@
 			  /* new */
 			  differences = TRUE;
 			  crm_xml_add(diff, prop_name, left_value);
+			  crm_xml_add(diff, XML_DIFF_MARKER, "modified");
 				      
 		      } else if(strcmp(left_value, right_val) == 0) {
 			  /* unchanged */
@@ -1623,6 +1624,7 @@
 			  /* changed */
 			  differences = TRUE;
 			  crm_xml_add(diff, prop_name, left_value);
+			  crm_xml_add(diff, XML_DIFF_MARKER, "modified");
 		      }
 		);
 
diff -r 86a1d0023036 -r 0212ad85975b xml/pacemaker.rng.in
--- a/xml/pacemaker.rng.in	Fri Jan 08 15:43:11 2010 +0100
+++ b/xml/pacemaker.rng.in	Mon Jan 11 13:47:39 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,79 @@
     </zeroOrMore>
   </define>
 
+  <define name="element-acls">
+    <element name="acls">
+      <zeroOrMore>
+	<choice>
+	  <element name="user">
+	    <attribute name="id"><text/></attribute>
+	    <interleave>
+	      <zeroOrMore>
+	        <element name="role_ref">
+	          <attribute name="id"><data type="IDREF"/></attribute>
+	        </element>
+	      </zeroOrMore>
+              <ref name="element-acl"/>
+	    </interleave>
+	  </element>
+	  <element name="role">
+	    <attribute name="id"><data type="ID"/></attribute>
+	    <interleave>
+	      <optional>
+	        <element name="members">
+	          <zeroOrMore>
+	            <element name="uid">
+                      <attribute name="id"><text/></attribute>
+	            </element>		  
+	          </zeroOrMore>
+	        </element>
+	      </optional>
+              <ref name="element-acl"/>
+	    </interleave>
+	  </element>
+	</choice>
+      </zeroOrMore>
+    </element>
+  </define>
+
+  <define name="element-acl">
+    <zeroOrMore>
+      <choice>
+	<element name="read">
+	  <attribute name="id"><data type="ID"/></attribute>
+	  <choice>
+	    <attribute name="ref"><data type="IDREF"/></attribute>
+	    <attribute name="tag"><text/></attribute>
+	    <group>
+	      <attribute name="ref"><data type="IDREF"/></attribute>
+	      <attribute name="tag"><text/></attribute>
+	    </group>
+	  </choice>
+	</element>
+	<element name="write">
+	  <attribute name="id"><data type="ID"/></attribute>
+	  <choice>
+	    <attribute name="ref"><data type="IDREF"/></attribute>
+	    <attribute name="tag"><text/></attribute>
+	    <group>
+	      <attribute name="ref"><data type="IDREF"/></attribute>
+	      <attribute name="tag"><text/></attribute>
+	    </group>
+	  </choice>
+	</element>
+	<element name="deny">
+	  <attribute name="id"><data type="ID"/></attribute>
+	  <choice>
+	    <attribute name="ref"><data type="IDREF"/></attribute>
+	    <attribute name="tag"><text/></attribute>
+	    <group>
+	      <attribute name="ref"><data type="IDREF"/></attribute>
+	      <attribute name="tag"><text/></attribute>
+	    </group>
+	  </choice>
+	</element>
+      </choice>
+    </zeroOrMore>
+  </define>
+
 </grammar>
_______________________________________________
Pacemaker mailing list
Pacemaker@oss.clusterlabs.org
http://oss.clusterlabs.org/mailman/listinfo/pacemaker

Reply via email to