Coverage Report

Created: 2025-11-26 06:09

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