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

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