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. pcmk__acl_annotate_permissions_recursive
  3. pcmk__acl_annotate_permissions
  4. pcmk__acl_evaled_render

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

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