root/lib/common/acl.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. free_acl
  2. pcmk__free_acls
  3. create_acl
  4. parse_acl_entry
  5. acl_to_text
  6. pcmk__apply_acl
  7. pcmk__unpack_acl
  8. pcmk__enable_acl
  9. test_acl_mode
  10. purge_xml_attributes
  11. xml_acl_filtered_copy
  12. implicitly_allowed
  13. pcmk__apply_creation_acl
  14. xml_acl_denied
  15. xml_acl_disable
  16. xml_acl_enabled
  17. pcmk__check_acl
  18. pcmk_acl_required
  19. pcmk__uid2username
  20. pcmk__update_acl_user

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

/* [previous][next][first][last][top][bottom][index][help] */