Line data Source code
1 : /*
2 : * Copyright 2004-2024 the Pacemaker project contributors
3 : *
4 : * The version control history for this file may have further details.
5 : *
6 : * This source code is licensed under the GNU Lesser General Public License
7 : * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
8 : */
9 :
10 : #include <crm_internal.h>
11 :
12 : #include <stdio.h>
13 : #include <sys/types.h>
14 : #include <pwd.h>
15 : #include <string.h>
16 : #include <stdlib.h>
17 : #include <stdarg.h>
18 :
19 : #include <libxml/tree.h>
20 :
21 : #include <crm/crm.h>
22 : #include <crm/common/xml.h>
23 : #include <crm/common/xml_internal.h>
24 : #include "crmcommon_private.h"
25 :
26 : typedef struct xml_acl_s {
27 : enum xml_private_flags mode;
28 : gchar *xpath;
29 : } xml_acl_t;
30 :
31 : static void
32 0 : free_acl(void *data)
33 : {
34 0 : if (data) {
35 0 : xml_acl_t *acl = data;
36 :
37 0 : g_free(acl->xpath);
38 0 : free(acl);
39 : }
40 0 : }
41 :
42 : void
43 0 : pcmk__free_acls(GList *acls)
44 : {
45 0 : g_list_free_full(acls, free_acl);
46 0 : }
47 :
48 : static GList *
49 0 : create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode)
50 : {
51 0 : xml_acl_t *acl = NULL;
52 :
53 0 : const char *tag = crm_element_value(xml, PCMK_XA_OBJECT_TYPE);
54 0 : const char *ref = crm_element_value(xml, PCMK_XA_REFERENCE);
55 0 : const char *xpath = crm_element_value(xml, PCMK_XA_XPATH);
56 0 : const char *attr = crm_element_value(xml, PCMK_XA_ATTRIBUTE);
57 :
58 0 : if (tag == NULL) {
59 : // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
60 0 : tag = crm_element_value(xml, PCMK_XA_TAG);
61 : }
62 0 : if (ref == NULL) {
63 : // @COMPAT Deprecated since 1.1.12 (needed for rolling upgrades)
64 0 : ref = crm_element_value(xml, PCMK__XA_REF);
65 : }
66 :
67 0 : if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) {
68 : // Schema should prevent this, but to be safe ...
69 0 : crm_trace("Ignoring ACL <%s> element without selection criteria",
70 : xml->name);
71 0 : return NULL;
72 : }
73 :
74 0 : acl = pcmk__assert_alloc(1, sizeof (xml_acl_t));
75 :
76 0 : acl->mode = mode;
77 0 : if (xpath) {
78 0 : acl->xpath = g_strdup(xpath);
79 0 : crm_trace("Unpacked ACL <%s> element using xpath: %s",
80 : xml->name, acl->xpath);
81 :
82 : } else {
83 0 : GString *buf = g_string_sized_new(128);
84 :
85 0 : if ((ref != NULL) && (attr != NULL)) {
86 : // NOTE: schema currently does not allow this
87 0 : pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
88 : ref, "' and @", attr, "]", NULL);
89 :
90 0 : } else if (ref != NULL) {
91 0 : pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@" PCMK_XA_ID "='",
92 : ref, "']", NULL);
93 :
94 0 : } else if (attr != NULL) {
95 0 : pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), "[@", attr, "]", NULL);
96 :
97 : } else {
98 0 : pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), NULL);
99 : }
100 :
101 0 : acl->xpath = buf->str;
102 :
103 0 : g_string_free(buf, FALSE);
104 0 : crm_trace("Unpacked ACL <%s> element as xpath: %s",
105 : xml->name, acl->xpath);
106 : }
107 :
108 0 : return g_list_append(acls, acl);
109 : }
110 :
111 : /*!
112 : * \internal
113 : * \brief Unpack a user, group, or role subtree of the ACLs section
114 : *
115 : * \param[in] acl_top XML of entire ACLs section
116 : * \param[in] acl_entry XML of ACL element being unpacked
117 : * \param[in,out] acls List of ACLs unpacked so far
118 : *
119 : * \return New head of (possibly modified) acls
120 : *
121 : * \note This function is recursive
122 : */
123 : static GList *
124 0 : parse_acl_entry(const xmlNode *acl_top, const xmlNode *acl_entry, GList *acls)
125 : {
126 0 : xmlNode *child = NULL;
127 :
128 0 : for (child = pcmk__xe_first_child(acl_entry, NULL, NULL, NULL);
129 0 : child != NULL; child = pcmk__xe_next(child)) {
130 :
131 0 : const char *tag = (const char *) child->name;
132 0 : const char *kind = crm_element_value(child, PCMK_XA_KIND);
133 :
134 0 : if (pcmk__xe_is(child, PCMK_XE_ACL_PERMISSION)) {
135 0 : CRM_ASSERT(kind != NULL);
136 0 : crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind);
137 0 : tag = kind;
138 : } else {
139 0 : crm_trace("Unpacking ACL <%s> element", tag);
140 : }
141 :
142 : /* @COMPAT PCMK__XE_ROLE_REF was deprecated in Pacemaker 1.1.12 (needed
143 : * for rolling upgrades)
144 : */
145 0 : if (pcmk__str_any_of(tag, PCMK_XE_ROLE, PCMK__XE_ROLE_REF, NULL)) {
146 0 : const char *ref_role = crm_element_value(child, PCMK_XA_ID);
147 :
148 0 : if (ref_role) {
149 0 : xmlNode *role = NULL;
150 :
151 0 : for (role = pcmk__xe_first_child(acl_top, NULL, NULL, NULL);
152 0 : role != NULL; role = pcmk__xe_next(role)) {
153 :
154 0 : if (!strcmp(PCMK_XE_ACL_ROLE, (const char *) role->name)) {
155 0 : const char *role_id = crm_element_value(role,
156 : PCMK_XA_ID);
157 :
158 0 : if (role_id && strcmp(ref_role, role_id) == 0) {
159 0 : crm_trace("Unpacking referenced role '%s' in ACL <%s> element",
160 : role_id, acl_entry->name);
161 0 : acls = parse_acl_entry(acl_top, role, acls);
162 0 : break;
163 : }
164 : }
165 : }
166 : }
167 :
168 : /* @COMPAT Use of a tag instead of a PCMK_XA_KIND attribute was
169 : * deprecated in 1.1.12. We still need to look for tags named
170 : * PCMK_VALUE_READ, etc., to support rolling upgrades. However,
171 : * eventually we can clean this up and make the variables more intuitive
172 : * (for example, don't assign a PCMK_XA_KIND value to the tag variable).
173 : */
174 0 : } else if (strcmp(tag, PCMK_VALUE_READ) == 0) {
175 0 : acls = create_acl(child, acls, pcmk__xf_acl_read);
176 :
177 0 : } else if (strcmp(tag, PCMK_VALUE_WRITE) == 0) {
178 0 : acls = create_acl(child, acls, pcmk__xf_acl_write);
179 :
180 0 : } else if (strcmp(tag, PCMK_VALUE_DENY) == 0) {
181 0 : acls = create_acl(child, acls, pcmk__xf_acl_deny);
182 :
183 : } else {
184 0 : crm_warn("Ignoring unknown ACL %s '%s'",
185 : (kind? "kind" : "element"), tag);
186 : }
187 : }
188 :
189 0 : return acls;
190 : }
191 :
192 : /*
193 : <acls>
194 : <acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
195 : <acl_role id="auto-l33t-haxor">
196 : <acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
197 : </acl_role>
198 : <acl_target id="niceguy">
199 : <role id="observer"/>
200 : </acl_target>
201 : <acl_role id="observer">
202 : <acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
203 : <acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
204 : <acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
205 : </acl_role>
206 : <acl_target id="badidea"><role id="auto-badidea"/></acl_target>
207 : <acl_role id="auto-badidea">
208 : <acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
209 : <acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
210 : </acl_role>
211 : </acls>
212 : */
213 :
214 : static const char *
215 0 : acl_to_text(enum xml_private_flags flags)
216 : {
217 0 : if (pcmk_is_set(flags, pcmk__xf_acl_deny)) {
218 0 : return "deny";
219 :
220 0 : } else if (pcmk_any_flags_set(flags, pcmk__xf_acl_write|pcmk__xf_acl_create)) {
221 0 : return "read/write";
222 :
223 0 : } else if (pcmk_is_set(flags, pcmk__xf_acl_read)) {
224 0 : return "read";
225 : }
226 0 : return "none";
227 : }
228 :
229 : void
230 0 : pcmk__apply_acl(xmlNode *xml)
231 : {
232 0 : GList *aIter = NULL;
233 0 : xml_doc_private_t *docpriv = xml->doc->_private;
234 : xml_node_private_t *nodepriv;
235 0 : xmlXPathObjectPtr xpathObj = NULL;
236 :
237 0 : if (!xml_acl_enabled(xml)) {
238 0 : crm_trace("Skipping ACLs for user '%s' because not enabled for this XML",
239 : docpriv->user);
240 0 : return;
241 : }
242 :
243 0 : for (aIter = docpriv->acls; aIter != NULL; aIter = aIter->next) {
244 0 : int max = 0, lpc = 0;
245 0 : xml_acl_t *acl = aIter->data;
246 :
247 0 : xpathObj = xpath_search(xml, acl->xpath);
248 0 : max = numXpathResults(xpathObj);
249 :
250 0 : for (lpc = 0; lpc < max; lpc++) {
251 0 : xmlNode *match = getXpathResult(xpathObj, lpc);
252 :
253 0 : nodepriv = match->_private;
254 0 : pcmk__set_xml_flags(nodepriv, acl->mode);
255 :
256 : // Build a GString only if tracing is enabled
257 0 : pcmk__if_tracing(
258 : {
259 : GString *path = pcmk__element_xpath(match);
260 : crm_trace("Applying %s ACL to %s matched by %s",
261 : acl_to_text(acl->mode), path->str, acl->xpath);
262 : g_string_free(path, TRUE);
263 : },
264 : {}
265 : );
266 : }
267 0 : crm_trace("Applied %s ACL %s (%d match%s)",
268 : acl_to_text(acl->mode), acl->xpath, max,
269 : ((max == 1)? "" : "es"));
270 0 : freeXpathObject(xpathObj);
271 : }
272 : }
273 :
274 : /*!
275 : * \internal
276 : * \brief Unpack ACLs for a given user into the
277 : * metadata of the target XML tree
278 : *
279 : * Taking the description of ACLs from the source XML tree and
280 : * marking up the target XML tree with access information for the
281 : * given user by tacking it onto the relevant nodes
282 : *
283 : * \param[in] source XML with ACL definitions
284 : * \param[in,out] target XML that ACLs will be applied to
285 : * \param[in] user Username whose ACLs need to be unpacked
286 : */
287 : void
288 0 : pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user)
289 : {
290 0 : xml_doc_private_t *docpriv = NULL;
291 :
292 0 : if ((target == NULL) || (target->doc == NULL)
293 0 : || (target->doc->_private == NULL)) {
294 0 : return;
295 : }
296 :
297 0 : docpriv = target->doc->_private;
298 0 : if (!pcmk_acl_required(user)) {
299 0 : crm_trace("Not unpacking ACLs because not required for user '%s'",
300 : user);
301 :
302 0 : } else if (docpriv->acls == NULL) {
303 0 : xmlNode *acls = get_xpath_object("//" PCMK_XE_ACLS, source, LOG_NEVER);
304 :
305 0 : pcmk__str_update(&docpriv->user, user);
306 :
307 0 : if (acls) {
308 0 : xmlNode *child = NULL;
309 :
310 0 : for (child = pcmk__xe_first_child(acls, NULL, NULL, NULL);
311 0 : child != NULL; child = pcmk__xe_next(child)) {
312 :
313 : /* @COMPAT PCMK__XE_ACL_USER was deprecated in Pacemaker 1.1.12
314 : * (needed for rolling upgrades)
315 : */
316 0 : if (pcmk__xe_is(child, PCMK_XE_ACL_TARGET)
317 0 : || pcmk__xe_is(child, PCMK__XE_ACL_USER)) {
318 0 : const char *id = crm_element_value(child, PCMK_XA_NAME);
319 :
320 0 : if (id == NULL) {
321 0 : id = crm_element_value(child, PCMK_XA_ID);
322 : }
323 :
324 0 : if (id && strcmp(id, user) == 0) {
325 0 : crm_debug("Unpacking ACLs for user '%s'", id);
326 0 : docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
327 : }
328 0 : } else if (pcmk__xe_is(child, PCMK_XE_ACL_GROUP)) {
329 0 : const char *id = crm_element_value(child, PCMK_XA_NAME);
330 :
331 0 : if (id == NULL) {
332 0 : id = crm_element_value(child, PCMK_XA_ID);
333 : }
334 :
335 0 : if (id && pcmk__is_user_in_group(user,id)) {
336 0 : crm_debug("Unpacking ACLs for group '%s'", id);
337 0 : docpriv->acls = parse_acl_entry(acls, child, docpriv->acls);
338 : }
339 : }
340 : }
341 : }
342 : }
343 : }
344 :
345 : /*!
346 : * \internal
347 : * \brief Copy source to target and set xf_acl_enabled flag in target
348 : *
349 : * \param[in] acl_source XML with ACL definitions
350 : * \param[in,out] target XML that ACLs will be applied to
351 : * \param[in] user Username whose ACLs need to be set
352 : */
353 : void
354 0 : pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
355 : {
356 0 : pcmk__unpack_acl(acl_source, target, user);
357 0 : pcmk__set_xml_doc_flag(target, pcmk__xf_acl_enabled);
358 0 : pcmk__apply_acl(target);
359 0 : }
360 :
361 : static inline bool
362 0 : test_acl_mode(enum xml_private_flags allowed, enum xml_private_flags requested)
363 : {
364 0 : if (pcmk_is_set(allowed, pcmk__xf_acl_deny)) {
365 0 : return false;
366 :
367 0 : } else if (pcmk_all_flags_set(allowed, requested)) {
368 0 : return true;
369 :
370 0 : } else if (pcmk_is_set(requested, pcmk__xf_acl_read)
371 0 : && pcmk_is_set(allowed, pcmk__xf_acl_write)) {
372 0 : return true;
373 :
374 0 : } else if (pcmk_is_set(requested, pcmk__xf_acl_create)
375 0 : && pcmk_any_flags_set(allowed, pcmk__xf_acl_write|pcmk__xf_created)) {
376 0 : return true;
377 : }
378 0 : return false;
379 : }
380 :
381 : /*!
382 : * \internal
383 : * \brief Rid XML tree of all unreadable nodes and node properties
384 : *
385 : * \param[in,out] xml Root XML node to be purged of attributes
386 : *
387 : * \return true if this node or any of its children are readable
388 : * if false is returned, xml will be freed
389 : *
390 : * \note This function is recursive
391 : */
392 : static bool
393 0 : purge_xml_attributes(xmlNode *xml)
394 : {
395 0 : xmlNode *child = NULL;
396 0 : xmlAttr *xIter = NULL;
397 0 : bool readable_children = false;
398 0 : xml_node_private_t *nodepriv = xml->_private;
399 :
400 0 : if (test_acl_mode(nodepriv->flags, pcmk__xf_acl_read)) {
401 0 : crm_trace("%s[@" PCMK_XA_ID "=%s] is readable",
402 : xml->name, pcmk__xe_id(xml));
403 0 : return true;
404 : }
405 :
406 0 : xIter = xml->properties;
407 0 : while (xIter != NULL) {
408 0 : xmlAttr *tmp = xIter;
409 0 : const char *prop_name = (const char *)xIter->name;
410 :
411 0 : xIter = xIter->next;
412 0 : if (strcmp(prop_name, PCMK_XA_ID) == 0) {
413 0 : continue;
414 : }
415 :
416 0 : xmlUnsetProp(xml, tmp->name);
417 : }
418 :
419 0 : child = pcmk__xml_first_child(xml);
420 0 : while ( child != NULL ) {
421 0 : xmlNode *tmp = child;
422 :
423 0 : child = pcmk__xml_next(child);
424 0 : readable_children |= purge_xml_attributes(tmp);
425 : }
426 :
427 0 : if (!readable_children) {
428 0 : free_xml(xml); /* Nothing readable under here, purge completely */
429 : }
430 0 : return readable_children;
431 : }
432 :
433 : /*!
434 : * \brief Copy ACL-allowed portions of specified XML
435 : *
436 : * \param[in] user Username whose ACLs should be used
437 : * \param[in] acl_source XML containing ACLs
438 : * \param[in] xml XML to be copied
439 : * \param[out] result Copy of XML portions readable via ACLs
440 : *
441 : * \return true if xml exists and ACLs are required for user, false otherwise
442 : * \note If this returns true, caller should use \p result rather than \p xml
443 : */
444 : bool
445 0 : xml_acl_filtered_copy(const char *user, xmlNode *acl_source, xmlNode *xml,
446 : xmlNode **result)
447 : {
448 0 : GList *aIter = NULL;
449 0 : xmlNode *target = NULL;
450 0 : xml_doc_private_t *docpriv = NULL;
451 :
452 0 : *result = NULL;
453 0 : if ((xml == NULL) || !pcmk_acl_required(user)) {
454 0 : crm_trace("Not filtering XML because ACLs not required for user '%s'",
455 : user);
456 0 : return false;
457 : }
458 :
459 0 : crm_trace("Filtering XML copy using user '%s' ACLs", user);
460 0 : target = pcmk__xml_copy(NULL, xml);
461 0 : if (target == NULL) {
462 0 : return true;
463 : }
464 :
465 0 : pcmk__enable_acl(acl_source, target, user);
466 :
467 0 : docpriv = target->doc->_private;
468 0 : for(aIter = docpriv->acls; aIter != NULL && target; aIter = aIter->next) {
469 0 : int max = 0;
470 0 : xml_acl_t *acl = aIter->data;
471 :
472 0 : if (acl->mode != pcmk__xf_acl_deny) {
473 : /* Nothing to do */
474 :
475 0 : } else if (acl->xpath) {
476 0 : int lpc = 0;
477 0 : xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
478 :
479 0 : max = numXpathResults(xpathObj);
480 0 : for(lpc = 0; lpc < max; lpc++) {
481 0 : xmlNode *match = getXpathResult(xpathObj, lpc);
482 :
483 0 : if (!purge_xml_attributes(match) && (match == target)) {
484 0 : crm_trace("ACLs deny user '%s' access to entire XML document",
485 : user);
486 0 : freeXpathObject(xpathObj);
487 0 : return true;
488 : }
489 : }
490 0 : crm_trace("ACLs deny user '%s' access to %s (%d %s)",
491 : user, acl->xpath, max,
492 : pcmk__plural_alt(max, "match", "matches"));
493 0 : freeXpathObject(xpathObj);
494 : }
495 : }
496 :
497 0 : if (!purge_xml_attributes(target)) {
498 0 : crm_trace("ACLs deny user '%s' access to entire XML document", user);
499 0 : return true;
500 : }
501 :
502 0 : if (docpriv->acls) {
503 0 : g_list_free_full(docpriv->acls, free_acl);
504 0 : docpriv->acls = NULL;
505 :
506 : } else {
507 0 : crm_trace("User '%s' without ACLs denied access to entire XML document",
508 : user);
509 0 : free_xml(target);
510 0 : target = NULL;
511 : }
512 :
513 0 : if (target) {
514 0 : *result = target;
515 : }
516 :
517 0 : return true;
518 : }
519 :
520 : /*!
521 : * \internal
522 : * \brief Check whether creation of an XML element is implicitly allowed
523 : *
524 : * Check whether XML is a "scaffolding" element whose creation is implicitly
525 : * allowed regardless of ACLs (that is, it is not in the ACL section and has
526 : * no attributes other than \c PCMK_XA_ID).
527 : *
528 : * \param[in] xml XML element to check
529 : *
530 : * \return true if XML element is implicitly allowed, false otherwise
531 : */
532 : static bool
533 0 : implicitly_allowed(const xmlNode *xml)
534 : {
535 0 : GString *path = NULL;
536 :
537 0 : for (xmlAttr *prop = xml->properties; prop != NULL; prop = prop->next) {
538 0 : if (strcmp((const char *) prop->name, PCMK_XA_ID) != 0) {
539 0 : return false;
540 : }
541 : }
542 :
543 0 : path = pcmk__element_xpath(xml);
544 0 : CRM_ASSERT(path != NULL);
545 :
546 0 : if (strstr((const char *) path->str, "/" PCMK_XE_ACLS "/") != NULL) {
547 0 : g_string_free(path, TRUE);
548 0 : return false;
549 : }
550 :
551 0 : g_string_free(path, TRUE);
552 0 : return true;
553 : }
554 :
555 : #define display_id(xml) pcmk__s(pcmk__xe_id(xml), "<unset>")
556 :
557 : /*!
558 : * \internal
559 : * \brief Drop XML nodes created in violation of ACLs
560 : *
561 : * Given an XML element, free all of its descendant nodes created in violation
562 : * of ACLs, with the exception of allowing "scaffolding" elements (i.e. those
563 : * that aren't in the ACL section and don't have any attributes other than
564 : * \c PCMK_XA_ID).
565 : *
566 : * \param[in,out] xml XML to check
567 : * \param[in] check_top Whether to apply checks to argument itself
568 : * (if true, xml might get freed)
569 : *
570 : * \note This function is recursive
571 : */
572 : void
573 0 : pcmk__apply_creation_acl(xmlNode *xml, bool check_top)
574 : {
575 0 : xml_node_private_t *nodepriv = xml->_private;
576 :
577 0 : if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) {
578 0 : if (implicitly_allowed(xml)) {
579 0 : crm_trace("Creation of <%s> scaffolding with " PCMK_XA_ID "=\"%s\""
580 : " is implicitly allowed",
581 : xml->name, display_id(xml));
582 :
583 0 : } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) {
584 0 : crm_trace("ACLs allow creation of <%s> with " PCMK_XA_ID "=\"%s\"",
585 : xml->name, display_id(xml));
586 :
587 0 : } else if (check_top) {
588 0 : crm_trace("ACLs disallow creation of <%s> with "
589 : PCMK_XA_ID "=\"%s\"", xml->name, display_id(xml));
590 0 : pcmk_free_xml_subtree(xml);
591 0 : return;
592 :
593 : } else {
594 0 : crm_notice("ACLs would disallow creation of %s<%s> with "
595 : PCMK_XA_ID "=\"%s\"",
596 : ((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""),
597 : xml->name, display_id(xml));
598 : }
599 : }
600 :
601 0 : for (xmlNode *cIter = pcmk__xml_first_child(xml); cIter != NULL; ) {
602 0 : xmlNode *child = cIter;
603 0 : cIter = pcmk__xml_next(cIter); /* In case it is free'd */
604 0 : pcmk__apply_creation_acl(child, true);
605 : }
606 : }
607 :
608 : /*!
609 : * \brief Check whether or not an XML node is ACL-denied
610 : *
611 : * \param[in] xml node to check
612 : *
613 : * \return true if XML node exists and is ACL-denied, false otherwise
614 : */
615 : bool
616 21 : xml_acl_denied(const xmlNode *xml)
617 : {
618 21 : if (xml && xml->doc && xml->doc->_private){
619 18 : xml_doc_private_t *docpriv = xml->doc->_private;
620 :
621 18 : return pcmk_is_set(docpriv->flags, pcmk__xf_acl_denied);
622 : }
623 3 : return false;
624 : }
625 :
626 : void
627 0 : xml_acl_disable(xmlNode *xml)
628 : {
629 0 : if (xml_acl_enabled(xml)) {
630 0 : xml_doc_private_t *docpriv = xml->doc->_private;
631 :
632 : /* Catch anything that was created but shouldn't have been */
633 0 : pcmk__apply_acl(xml);
634 0 : pcmk__apply_creation_acl(xml, false);
635 0 : pcmk__clear_xml_flags(docpriv, pcmk__xf_acl_enabled);
636 : }
637 0 : }
638 :
639 : /*!
640 : * \brief Check whether or not an XML node is ACL-enabled
641 : *
642 : * \param[in] xml node to check
643 : *
644 : * \return true if XML node exists and is ACL-enabled, false otherwise
645 : */
646 : bool
647 64 : xml_acl_enabled(const xmlNode *xml)
648 : {
649 64 : if (xml && xml->doc && xml->doc->_private){
650 61 : xml_doc_private_t *docpriv = xml->doc->_private;
651 :
652 61 : return pcmk_is_set(docpriv->flags, pcmk__xf_acl_enabled);
653 : }
654 3 : return false;
655 : }
656 :
657 : bool
658 0 : pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
659 : {
660 0 : CRM_ASSERT(xml);
661 0 : CRM_ASSERT(xml->doc);
662 0 : CRM_ASSERT(xml->doc->_private);
663 :
664 0 : if (pcmk__tracking_xml_changes(xml, false) && xml_acl_enabled(xml)) {
665 0 : xmlNode *parent = xml;
666 0 : xml_doc_private_t *docpriv = xml->doc->_private;
667 0 : GString *xpath = NULL;
668 :
669 0 : if (docpriv->acls == NULL) {
670 0 : pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
671 :
672 0 : pcmk__if_tracing({}, return false);
673 0 : xpath = pcmk__element_xpath(xml);
674 0 : if (name != NULL) {
675 0 : pcmk__g_strcat(xpath, "[@", name, "]", NULL);
676 : }
677 :
678 0 : qb_log_from_external_source(__func__, __FILE__,
679 : "User '%s' without ACLs denied %s "
680 : "access to %s", LOG_TRACE, __LINE__, 0,
681 : docpriv->user, acl_to_text(mode),
682 0 : (const char *) xpath->str);
683 0 : g_string_free(xpath, TRUE);
684 0 : return false;
685 : }
686 :
687 : /* Walk the tree upwards looking for xml_acl_* flags
688 : * - Creating an attribute requires write permissions for the node
689 : * - Creating a child requires write permissions for the parent
690 : */
691 :
692 0 : if (name) {
693 0 : xmlAttr *attr = xmlHasProp(xml, (pcmkXmlStr) name);
694 :
695 0 : if (attr && mode == pcmk__xf_acl_create) {
696 0 : mode = pcmk__xf_acl_write;
697 : }
698 : }
699 :
700 0 : while (parent && parent->_private) {
701 0 : xml_node_private_t *nodepriv = parent->_private;
702 0 : if (test_acl_mode(nodepriv->flags, mode)) {
703 0 : return true;
704 :
705 0 : } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_acl_deny)) {
706 0 : pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
707 :
708 0 : pcmk__if_tracing({}, return false);
709 0 : xpath = pcmk__element_xpath(xml);
710 0 : if (name != NULL) {
711 0 : pcmk__g_strcat(xpath, "[@", name, "]", NULL);
712 : }
713 :
714 0 : qb_log_from_external_source(__func__, __FILE__,
715 : "%sACL denies user '%s' %s access "
716 : "to %s", LOG_TRACE, __LINE__, 0,
717 : (parent != xml)? "Parent ": "",
718 : docpriv->user, acl_to_text(mode),
719 0 : (const char *) xpath->str);
720 0 : g_string_free(xpath, TRUE);
721 0 : return false;
722 : }
723 0 : parent = parent->parent;
724 : }
725 :
726 0 : pcmk__set_xml_doc_flag(xml, pcmk__xf_acl_denied);
727 :
728 0 : pcmk__if_tracing({}, return false);
729 0 : xpath = pcmk__element_xpath(xml);
730 0 : if (name != NULL) {
731 0 : pcmk__g_strcat(xpath, "[@", name, "]", NULL);
732 : }
733 :
734 0 : qb_log_from_external_source(__func__, __FILE__,
735 : "Default ACL denies user '%s' %s access to "
736 : "%s", LOG_TRACE, __LINE__, 0,
737 : docpriv->user, acl_to_text(mode),
738 0 : (const char *) xpath->str);
739 0 : g_string_free(xpath, TRUE);
740 0 : return false;
741 : }
742 :
743 0 : return true;
744 : }
745 :
746 : /*!
747 : * \brief Check whether ACLs are required for a given user
748 : *
749 : * \param[in] User name to check
750 : *
751 : * \return true if the user requires ACLs, false otherwise
752 : */
753 : bool
754 98 : pcmk_acl_required(const char *user)
755 : {
756 98 : if (pcmk__str_empty(user)) {
757 95 : crm_trace("ACLs not required because no user set");
758 95 : return false;
759 :
760 3 : } else if (!strcmp(user, CRM_DAEMON_USER) || !strcmp(user, "root")) {
761 2 : crm_trace("ACLs not required for privileged user %s", user);
762 2 : return false;
763 : }
764 1 : crm_trace("ACLs required for %s", user);
765 1 : return true;
766 : }
767 :
768 : char *
769 0 : pcmk__uid2username(uid_t uid)
770 : {
771 0 : struct passwd *pwent = getpwuid(uid);
772 :
773 0 : if (pwent == NULL) {
774 0 : crm_perror(LOG_INFO, "Cannot get user details for user ID %d", uid);
775 0 : return NULL;
776 : }
777 0 : return pcmk__str_copy(pwent->pw_name);
778 : }
779 :
780 : /*!
781 : * \internal
782 : * \brief Set the ACL user field properly on an XML request
783 : *
784 : * Multiple user names are potentially involved in an XML request: the effective
785 : * user of the current process; the user name known from an IPC client
786 : * connection; and the user name obtained from the request itself, whether by
787 : * the current standard XML attribute name or an older legacy attribute name.
788 : * This function chooses the appropriate one that should be used for ACLs, sets
789 : * it in the request (using the standard attribute name, and the legacy name if
790 : * given), and returns it.
791 : *
792 : * \param[in,out] request XML request to update
793 : * \param[in] field Alternate name for ACL user name XML attribute
794 : * \param[in] peer_user User name as known from IPC connection
795 : *
796 : * \return ACL user name actually used
797 : */
798 : const char *
799 0 : pcmk__update_acl_user(xmlNode *request, const char *field,
800 : const char *peer_user)
801 : {
802 : static const char *effective_user = NULL;
803 0 : const char *requested_user = NULL;
804 0 : const char *user = NULL;
805 :
806 0 : if (effective_user == NULL) {
807 0 : effective_user = pcmk__uid2username(geteuid());
808 0 : if (effective_user == NULL) {
809 0 : effective_user = pcmk__str_copy("#unprivileged");
810 0 : crm_err("Unable to determine effective user, assuming unprivileged for ACLs");
811 : }
812 : }
813 :
814 0 : requested_user = crm_element_value(request, PCMK_XE_ACL_TARGET);
815 0 : if (requested_user == NULL) {
816 : /* @COMPAT rolling upgrades <=1.1.11
817 : *
818 : * field is checked for backward compatibility with older versions that
819 : * did not use PCMK_XE_ACL_TARGET.
820 : */
821 0 : requested_user = crm_element_value(request, field);
822 : }
823 :
824 0 : if (!pcmk__is_privileged(effective_user)) {
825 : /* We're not running as a privileged user, set or overwrite any existing
826 : * value for PCMK_XE_ACL_TARGET
827 : */
828 0 : user = effective_user;
829 :
830 0 : } else if (peer_user == NULL && requested_user == NULL) {
831 : /* No user known or requested, use 'effective_user' and make sure one is
832 : * set for the request
833 : */
834 0 : user = effective_user;
835 :
836 0 : } else if (peer_user == NULL) {
837 : /* No user known, trusting 'requested_user' */
838 0 : user = requested_user;
839 :
840 0 : } else if (!pcmk__is_privileged(peer_user)) {
841 : /* The peer is not a privileged user, set or overwrite any existing
842 : * value for PCMK_XE_ACL_TARGET
843 : */
844 0 : user = peer_user;
845 :
846 0 : } else if (requested_user == NULL) {
847 : /* Even if we're privileged, make sure there is always a value set */
848 0 : user = peer_user;
849 :
850 : } else {
851 : /* Legal delegation to 'requested_user' */
852 0 : user = requested_user;
853 : }
854 :
855 : // This requires pointer comparison, not string comparison
856 0 : if (user != crm_element_value(request, PCMK_XE_ACL_TARGET)) {
857 0 : crm_xml_add(request, PCMK_XE_ACL_TARGET, user);
858 : }
859 :
860 0 : if (field != NULL && user != crm_element_value(request, field)) {
861 0 : crm_xml_add(request, field, user);
862 : }
863 :
864 0 : return requested_user;
865 : }
|