root/lib/pacemaker/pcmk_acl.c

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

DEFINITIONS

This source file includes following definitions.
  1. pcmk__acl_mark_node_with_namespace
  2. annotate_with_siblings
  3. pcmk__acl_annotate_permissions
  4. pcmk__acl_evaled_render

   1 /*
   2  * Copyright 2004-2023 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/parser.h>
  20 #include <libxml/tree.h>
  21 #include <libxml/xpath.h>
  22 #include <libxslt/transform.h>
  23 #include <libxslt/variables.h>
  24 #include <libxslt/xsltutils.h>
  25 
  26 #include <crm/crm.h>
  27 #include <crm/msg_xml.h>
  28 #include <crm/common/xml.h>
  29 #include <crm/common/xml_internal.h>
  30 #include <crm/common/internal.h>
  31 
  32 #include <pacemaker-internal.h>
  33 
  34 #define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
  35 #define ACL_NS_Q_PREFIX  "pcmk-access-"
  36 #define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX   "writable"
  37 #define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX   "readable"
  38 #define ACL_NS_Q_DENIED   (const xmlChar *) ACL_NS_Q_PREFIX   "denied"
  39 
  40 static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
  41 static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
  42 static const xmlChar *NS_DENIED =   (const xmlChar *) ACL_NS_PREFIX "denied";
  43 
  44 /*!
  45  * \brief This function takes a node and marks it with the namespace
  46  *        given in the ns parameter.
  47  *
  48  * \param[in,out] i_node
  49  * \param[in] ns
  50  * \param[in,out] ret
  51  * \param[in,out] ns_recycle_writable
  52  * \param[in,out] ns_recycle_readable
  53  * \param[in,out] ns_recycle_denied
  54  */
  55 static void
  56 pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
     /* [previous][next][first][last][top][bottom][index][help] */
  57                                    xmlNs **ns_recycle_writable,
  58                                    xmlNs **ns_recycle_readable,
  59                                    xmlNs **ns_recycle_denied)
  60 {
  61     if (ns == NS_WRITABLE)
  62     {
  63         if (*ns_recycle_writable == NULL)
  64         {
  65             *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  66                                            NS_WRITABLE, ACL_NS_Q_WRITABLE);
  67         }
  68         xmlSetNs(i_node, *ns_recycle_writable);
  69         *ret = pcmk_rc_ok;
  70     }
  71     else if (ns == NS_READABLE)
  72     {
  73         if (*ns_recycle_readable == NULL)
  74         {
  75             *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  76                                            NS_READABLE, ACL_NS_Q_READABLE);
  77         }
  78         xmlSetNs(i_node, *ns_recycle_readable);
  79         *ret = pcmk_rc_ok;
  80     }
  81     else if (ns == NS_DENIED)
  82     {
  83         if (*ns_recycle_denied == NULL)
  84         {
  85             *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
  86                                          NS_DENIED, ACL_NS_Q_DENIED);
  87         };
  88         xmlSetNs(i_node, *ns_recycle_denied);
  89         *ret = pcmk_rc_ok;
  90     }
  91 }
  92 
  93 /*!
  94  * \brief Annotate a given XML element or property and its siblings with
  95  *        XML namespaces to indicate ACL permissions
  96  *
  97  * \param[in,out] xml_modify  XML to annotate
  98  *
  99  * \return  A standard Pacemaker return code
 100  *          Namely:
 101  *          - pcmk_rc_ok upon success,
 102  *          - pcmk_rc_already if ACLs were not applicable,
 103  *          - pcmk_rc_schema_validation if the validation schema version
 104  *              is unsupported (see note), or
 105  *          - EINVAL or ENOMEM as appropriate;
 106  *
 107  * \note This function is recursive
 108  */
 109 static int
 110 annotate_with_siblings(xmlNode *xml_modify)
     /* [previous][next][first][last][top][bottom][index][help] */
 111 {
 112 
 113     static xmlNs *ns_recycle_writable = NULL,
 114                  *ns_recycle_readable = NULL,
 115                  *ns_recycle_denied = NULL;
 116     static const xmlDoc *prev_doc = NULL;
 117 
 118     xmlNode *i_node = NULL;
 119     const xmlChar *ns;
 120     int ret = EINVAL; // nodes have not been processed yet
 121 
 122     if (prev_doc == NULL || prev_doc != xml_modify->doc) {
 123         prev_doc = xml_modify->doc;
 124         ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
 125     }
 126 
 127     for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
 128         switch (i_node->type) {
 129             case XML_ELEMENT_NODE:
 130                 pcmk__set_xml_doc_flag(i_node, pcmk__xf_tracking);
 131 
 132                 if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
 133                     ns = NS_DENIED;
 134                 } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
 135                     ns = NS_READABLE;
 136                 } else {
 137                     ns = NS_WRITABLE;
 138                 }
 139                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
 140                                                    &ns_recycle_writable,
 141                                                    &ns_recycle_readable,
 142                                                    &ns_recycle_denied);
 143                 // @TODO Could replace recursion with iteration to save stack
 144                 if (i_node->properties != NULL) {
 145                     /* This is not entirely clear, but relies on the very same
 146                      * class-hierarchy emulation that libxml2 has firmly baked
 147                      * in its API/ABI
 148                      */
 149                     ret |= annotate_with_siblings((xmlNodePtr)
 150                                                   i_node->properties);
 151                 }
 152                 if (i_node->children != NULL) {
 153                     ret |= annotate_with_siblings(i_node->children);
 154                 }
 155                 break;
 156 
 157             case XML_ATTRIBUTE_NODE:
 158                 // We can utilize that parent has already been assigned the ns
 159                 if (!pcmk__check_acl(i_node->parent,
 160                                      (const char *) i_node->name,
 161                                      pcmk__xf_acl_read)) {
 162                     ns = NS_DENIED;
 163                 } else if (!pcmk__check_acl(i_node,
 164                                        (const char *) i_node->name,
 165                                        pcmk__xf_acl_write)) {
 166                     ns = NS_READABLE;
 167                 } else {
 168                     ns = NS_WRITABLE;
 169                 }
 170                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
 171                                                    &ns_recycle_writable,
 172                                                    &ns_recycle_readable,
 173                                                    &ns_recycle_denied);
 174                 break;
 175 
 176             case XML_COMMENT_NODE:
 177                 // We can utilize that parent has already been assigned the ns
 178                 if (!pcmk__check_acl(i_node->parent,
 179                                      (const char *) i_node->name,
 180                                      pcmk__xf_acl_read)) {
 181                     ns = NS_DENIED;
 182                 } else if (!pcmk__check_acl(i_node->parent,
 183                                             (const char *) i_node->name,
 184                                             pcmk__xf_acl_write)) {
 185                     ns = NS_READABLE;
 186                 } else {
 187                     ns = NS_WRITABLE;
 188                 }
 189                 pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
 190                                                    &ns_recycle_writable,
 191                                                    &ns_recycle_readable,
 192                                                    &ns_recycle_denied);
 193                 break;
 194 
 195             default:
 196                 break;
 197         }
 198     }
 199 
 200     return ret;
 201 }
 202 
 203 int
 204 pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
     /* [previous][next][first][last][top][bottom][index][help] */
 205                                xmlDoc **acl_evaled_doc)
 206 {
 207     int ret, version;
 208     xmlNode *target, *comment;
 209     const char *validation;
 210 
 211     CRM_CHECK(cred != NULL, return EINVAL);
 212     CRM_CHECK(cib_doc != NULL, return EINVAL);
 213     CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
 214 
 215     /* avoid trivial accidental XML injection */
 216     if (strpbrk(cred, "<>&") != NULL) {
 217         return EINVAL;
 218     }
 219 
 220     if (!pcmk_acl_required(cred)) {
 221         /* nothing to evaluate */
 222         return pcmk_rc_already;
 223     }
 224 
 225     // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
 226 
 227     validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
 228                                    XML_ATTR_VALIDATION);
 229     version = get_schema_version(validation);
 230     if (get_schema_version(PCMK__COMPAT_ACL_2_MIN_INCL) > version) {
 231         return pcmk_rc_schema_validation;
 232     }
 233 
 234     target = copy_xml(xmlDocGetRootElement((xmlDoc *) cib_doc));
 235     if (target == NULL) {
 236         return EINVAL;
 237     }
 238 
 239     pcmk__enable_acl(target, target, cred);
 240 
 241     ret = annotate_with_siblings(target);
 242 
 243     if (ret == pcmk_rc_ok) {
 244         char *credentials = crm_strdup_printf("ACLs as evaluated for user %s",
 245                                               cred);
 246 
 247         comment = xmlNewDocComment(target->doc, (pcmkXmlStr) credentials);
 248         free(credentials);
 249         if (comment == NULL) {
 250             xmlFreeNode(target);
 251             return EINVAL;
 252         }
 253         xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
 254         *acl_evaled_doc = target->doc;
 255         return pcmk_rc_ok;
 256     } else {
 257         xmlFreeNode(target);
 258         return ret; //for now, it should be some kind of error
 259     }
 260 }
 261 
 262 int
 263 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
     /* [previous][next][first][last][top][bottom][index][help] */
 264                         xmlChar **doc_txt_ptr)
 265 {
 266     xmlDoc *xslt_doc;
 267     xsltStylesheet *xslt;
 268     xsltTransformContext *xslt_ctxt;
 269     xmlDoc *res;
 270     char *sfile;
 271     static const char *params_namespace[] = {
 272         "accessrendercfg:c-writable",           ACL_NS_Q_PREFIX "writable:",
 273         "accessrendercfg:c-readable",           ACL_NS_Q_PREFIX "readable:",
 274         "accessrendercfg:c-denied",             ACL_NS_Q_PREFIX "denied:",
 275         "accessrendercfg:c-reset",              "",
 276         "accessrender:extra-spacing",           "no",
 277         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
 278         NULL
 279     }, *params_useansi[] = {
 280         /* start with hard-coded defaults, then adapt per the template ones */
 281         "accessrendercfg:c-writable",           "\x1b[32m",
 282         "accessrendercfg:c-readable",           "\x1b[34m",
 283         "accessrendercfg:c-denied",             "\x1b[31m",
 284         "accessrendercfg:c-reset",              "\x1b[0m",
 285         "accessrender:extra-spacing",           "no",
 286         "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
 287         NULL
 288     }, *params_noansi[] = {
 289         "accessrendercfg:c-writable",           "vvv---[ WRITABLE ]---vvv",
 290         "accessrendercfg:c-readable",           "vvv---[ READABLE ]---vvv",
 291         "accessrendercfg:c-denied",             "vvv---[ ~DENIED~ ]---vvv",
 292         "accessrendercfg:c-reset",              "",
 293         "accessrender:extra-spacing",           "yes",
 294         "accessrender:self-reproducing-prefix", "",
 295         NULL
 296     };
 297     const char **params;
 298     int ret;
 299     xmlParserCtxtPtr parser_ctxt;
 300 
 301     /* unfortunately, the input (coming from CIB originally) was parsed with
 302        blanks ignored, and since the output is a conversion of XML to text
 303        format (we would be covered otherwise thanks to implicit
 304        pretty-printing), we need to dump the tree to string output first,
 305        only to subsequently reparse it -- this time with blanks honoured */
 306     xmlChar *annotated_dump;
 307     int dump_size;
 308 
 309     CRM_ASSERT(how != pcmk__acl_render_none);
 310 
 311     // Color is the default render mode for terminals; text is default otherwise
 312     if (how == pcmk__acl_render_default) {
 313         if (isatty(STDOUT_FILENO)) {
 314             how = pcmk__acl_render_color;
 315         } else {
 316             how = pcmk__acl_render_text;
 317         }
 318     }
 319 
 320     xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
 321     res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
 322                      XML_PARSE_NONET);
 323     CRM_ASSERT(res != NULL);
 324     xmlFree(annotated_dump);
 325     xmlFreeDoc(annotated_doc);
 326     annotated_doc = res;
 327 
 328     sfile = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_base_xslt,
 329                                     "access-render-2");
 330     parser_ctxt = xmlNewParserCtxt();
 331 
 332     CRM_ASSERT(sfile != NULL);
 333     CRM_ASSERT(parser_ctxt != NULL);
 334 
 335     xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
 336 
 337     xslt = xsltParseStylesheetDoc(xslt_doc);  /* acquires xslt_doc! */
 338     if (xslt == NULL) {
 339         crm_crit("Problem in parsing %s", sfile);
 340         return EINVAL;
 341     }
 342     free(sfile);
 343     sfile = NULL;
 344     xmlFreeParserCtxt(parser_ctxt);
 345 
 346     xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
 347     CRM_ASSERT(xslt_ctxt != NULL);
 348 
 349     switch (how) {
 350         case pcmk__acl_render_namespace:
 351             params = params_namespace;
 352             break;
 353         case pcmk__acl_render_text:
 354             params = params_noansi;
 355             break;
 356         default:
 357             /* pcmk__acl_render_color is the only remaining option.
 358              * The compiler complains about params possibly uninitialized if we
 359              * don't use default here.
 360              */
 361             params = params_useansi;
 362             break;
 363     }
 364 
 365     xsltQuoteUserParams(xslt_ctxt, params);
 366 
 367     res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
 368                                   NULL, NULL, xslt_ctxt);
 369 
 370     xmlFreeDoc(annotated_doc);
 371     annotated_doc = NULL;
 372     xsltFreeTransformContext(xslt_ctxt);
 373     xslt_ctxt = NULL;
 374 
 375     if (how == pcmk__acl_render_color && params != params_useansi) {
 376         char **param_i = (char **) params;
 377         do {
 378             free(*param_i);
 379         } while (*param_i++ != NULL);
 380         free(params);
 381     }
 382 
 383     if (res == NULL) {
 384         ret = EINVAL;
 385     } else {
 386         int doc_txt_len;
 387         int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
 388         xmlFreeDoc(res);
 389         if (temp == 0) {
 390             ret = pcmk_rc_ok;
 391         } else {
 392             ret = EINVAL;
 393         }
 394     }
 395     xsltFreeStylesheet(xslt);
 396     return ret;
 397 }

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