pacemaker  2.1.9-49aab99839
Scalable High-Availability cluster resource manager
pcmk_acl.c
Go to the documentation of this file.
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/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/common/xml.h>
29 #include <crm/common/internal.h>
30 
31 #include <pacemaker-internal.h>
32 
33 #define ACL_NS_PREFIX "http://clusterlabs.org/ns/pacemaker/access/"
34 #define ACL_NS_Q_PREFIX "pcmk-access-"
35 #define ACL_NS_Q_WRITABLE (const xmlChar *) ACL_NS_Q_PREFIX "writable"
36 #define ACL_NS_Q_READABLE (const xmlChar *) ACL_NS_Q_PREFIX "readable"
37 #define ACL_NS_Q_DENIED (const xmlChar *) ACL_NS_Q_PREFIX "denied"
38 
39 static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
40 static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
41 static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
42 
54 static void
55 pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
56  xmlNs **ns_recycle_writable,
57  xmlNs **ns_recycle_readable,
58  xmlNs **ns_recycle_denied)
59 {
60  if (ns == NS_WRITABLE)
61  {
62  if (*ns_recycle_writable == NULL)
63  {
64  *ns_recycle_writable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
65  NS_WRITABLE, ACL_NS_Q_WRITABLE);
66  }
67  xmlSetNs(i_node, *ns_recycle_writable);
68  *ret = pcmk_rc_ok;
69  }
70  else if (ns == NS_READABLE)
71  {
72  if (*ns_recycle_readable == NULL)
73  {
74  *ns_recycle_readable = xmlNewNs(xmlDocGetRootElement(i_node->doc),
75  NS_READABLE, ACL_NS_Q_READABLE);
76  }
77  xmlSetNs(i_node, *ns_recycle_readable);
78  *ret = pcmk_rc_ok;
79  }
80  else if (ns == NS_DENIED)
81  {
82  if (*ns_recycle_denied == NULL)
83  {
84  *ns_recycle_denied = xmlNewNs(xmlDocGetRootElement(i_node->doc),
85  NS_DENIED, ACL_NS_Q_DENIED);
86  };
87  xmlSetNs(i_node, *ns_recycle_denied);
88  *ret = pcmk_rc_ok;
89  }
90 }
91 
108 static int
109 annotate_with_siblings(xmlNode *xml_modify)
110 {
111 
112  static xmlNs *ns_recycle_writable = NULL,
113  *ns_recycle_readable = NULL,
114  *ns_recycle_denied = NULL;
115  static const xmlDoc *prev_doc = NULL;
116 
117  xmlNode *i_node = NULL;
118  const xmlChar *ns;
119  int ret = EINVAL; // nodes have not been processed yet
120 
121  if (prev_doc == NULL || prev_doc != xml_modify->doc) {
122  prev_doc = xml_modify->doc;
123  ns_recycle_writable = ns_recycle_readable = ns_recycle_denied = NULL;
124  }
125 
126  for (i_node = xml_modify; i_node != NULL; i_node = i_node->next) {
127  switch (i_node->type) {
128  case XML_ELEMENT_NODE:
130 
131  if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_read)) {
132  ns = NS_DENIED;
133  } else if (!pcmk__check_acl(i_node, NULL, pcmk__xf_acl_write)) {
134  ns = NS_READABLE;
135  } else {
136  ns = NS_WRITABLE;
137  }
138  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
139  &ns_recycle_writable,
140  &ns_recycle_readable,
141  &ns_recycle_denied);
142  // @TODO Could replace recursion with iteration to save stack
143  if (i_node->properties != NULL) {
144  /* This is not entirely clear, but relies on the very same
145  * class-hierarchy emulation that libxml2 has firmly baked
146  * in its API/ABI
147  */
148  ret |= annotate_with_siblings((xmlNodePtr)
149  i_node->properties);
150  }
151  if (i_node->children != NULL) {
152  ret |= annotate_with_siblings(i_node->children);
153  }
154  break;
155 
156  case XML_ATTRIBUTE_NODE:
157  // We can utilize that parent has already been assigned the ns
158  if (!pcmk__check_acl(i_node->parent,
159  (const char *) i_node->name,
161  ns = NS_DENIED;
162  } else if (!pcmk__check_acl(i_node,
163  (const char *) i_node->name,
165  ns = NS_READABLE;
166  } else {
167  ns = NS_WRITABLE;
168  }
169  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
170  &ns_recycle_writable,
171  &ns_recycle_readable,
172  &ns_recycle_denied);
173  break;
174 
175  case XML_COMMENT_NODE:
176  // We can utilize that parent has already been assigned the ns
177  if (!pcmk__check_acl(i_node->parent,
178  (const char *) i_node->name,
180  ns = NS_DENIED;
181  } else if (!pcmk__check_acl(i_node->parent,
182  (const char *) i_node->name,
184  ns = NS_READABLE;
185  } else {
186  ns = NS_WRITABLE;
187  }
188  pcmk__acl_mark_node_with_namespace(i_node, ns, &ret,
189  &ns_recycle_writable,
190  &ns_recycle_readable,
191  &ns_recycle_denied);
192  break;
193 
194  default:
195  break;
196  }
197  }
198 
199  return ret;
200 }
201 
202 int
203 pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc,
204  xmlDoc **acl_evaled_doc)
205 {
206  int ret;
207  xmlNode *target, *comment;
208  const char *validation;
209 
210  CRM_CHECK(cred != NULL, return EINVAL);
211  CRM_CHECK(cib_doc != NULL, return EINVAL);
212  CRM_CHECK(acl_evaled_doc != NULL, return EINVAL);
213 
214  /* avoid trivial accidental XML injection */
215  if (strpbrk(cred, "<>&") != NULL) {
216  return EINVAL;
217  }
218 
219  if (!pcmk_acl_required(cred)) {
220  /* nothing to evaluate */
221  return pcmk_rc_already;
222  }
223 
224  // @COMPAT xmlDocGetRootElement() requires non-const in libxml2 < 2.9.2
225  validation = crm_element_value(xmlDocGetRootElement((xmlDoc *) cib_doc),
227 
229  validation) > 0) {
231  }
232 
233  target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
234  if (target == NULL) {
235  return EINVAL;
236  }
237 
239 
240  ret = annotate_with_siblings(target);
241 
242  if (ret == pcmk_rc_ok) {
243  char *content = crm_strdup_printf("ACLs as evaluated for user %s",
244  cred);
245 
246  comment = pcmk__xc_create(target->doc, content);
247  xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
248  *acl_evaled_doc = target->doc;
249  free(content);
250 
251  } else {
252  free_xml(target);
253  }
254  return ret;
255 }
256 
257 int
258 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
259  xmlChar **doc_txt_ptr)
260 {
261  xmlDoc *xslt_doc;
262  xsltStylesheet *xslt;
263  xsltTransformContext *xslt_ctxt;
264  xmlDoc *res;
265  char *sfile;
266  static const char *params_namespace[] = {
267  "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
268  "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
269  "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
270  "accessrendercfg:c-reset", "",
271  "accessrender:extra-spacing", "no",
272  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
273  NULL
274  }, *params_useansi[] = {
275  /* start with hard-coded defaults, then adapt per the template ones */
276  "accessrendercfg:c-writable", "\x1b[32m",
277  "accessrendercfg:c-readable", "\x1b[34m",
278  "accessrendercfg:c-denied", "\x1b[31m",
279  "accessrendercfg:c-reset", "\x1b[0m",
280  "accessrender:extra-spacing", "no",
281  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
282  NULL
283  }, *params_noansi[] = {
284  "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
285  "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
286  "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
287  "accessrendercfg:c-reset", "",
288  "accessrender:extra-spacing", "yes",
289  "accessrender:self-reproducing-prefix", "",
290  NULL
291  };
292  const char **params;
293  int rc = pcmk_rc_ok;
294  xmlParserCtxtPtr parser_ctxt;
295 
296  /* unfortunately, the input (coming from CIB originally) was parsed with
297  blanks ignored, and since the output is a conversion of XML to text
298  format (we would be covered otherwise thanks to implicit
299  pretty-printing), we need to dump the tree to string output first,
300  only to subsequently reparse it -- this time with blanks honoured */
301  xmlChar *annotated_dump;
302  int dump_size;
303 
305 
306  // Color is the default render mode for terminals; text is default otherwise
307  if (how == pcmk__acl_render_default) {
308  if (isatty(STDOUT_FILENO)) {
310  } else {
311  how = pcmk__acl_render_text;
312  }
313  }
314 
315  xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
316 
317  /* res does not need private data: it's temporary and used only with libxslt
318  * functions
319  */
320  res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
321  XML_PARSE_NONET);
322  pcmk__assert(res != NULL);
323  xmlFree(annotated_dump);
324  pcmk__xml_free_doc(annotated_doc);
325  annotated_doc = res;
326 
328  "access-render-2");
329  parser_ctxt = xmlNewParserCtxt();
330 
331  pcmk__assert(sfile != NULL);
332  pcmk__mem_assert(parser_ctxt);
333 
334  xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
335 
336  xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
337  if (xslt == NULL) {
338  crm_crit("Problem in parsing %s", sfile);
339  rc = EINVAL;
340  goto done;
341  }
342  xmlFreeParserCtxt(parser_ctxt);
343 
344  xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
345  pcmk__mem_assert(xslt_ctxt);
346 
347  switch (how) {
349  params = params_namespace;
350  break;
352  params = params_noansi;
353  break;
354  default:
355  /* pcmk__acl_render_color is the only remaining option.
356  * The compiler complains about params possibly uninitialized if we
357  * don't use default here.
358  */
359  params = params_useansi;
360  break;
361  }
362 
363  xsltQuoteUserParams(xslt_ctxt, params);
364 
365  res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
366  NULL, NULL, xslt_ctxt);
367 
368  pcmk__xml_free_doc(annotated_doc);
369  annotated_doc = NULL;
370  xsltFreeTransformContext(xslt_ctxt);
371  xslt_ctxt = NULL;
372 
373  if (how == pcmk__acl_render_color && params != params_useansi) {
374  char **param_i = (char **) params;
375  do {
376  free(*param_i);
377  } while (*param_i++ != NULL);
378  free(params);
379  }
380 
381  if (res == NULL) {
382  rc = EINVAL;
383  } else {
384  int doc_txt_len;
385  int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
386 
387  pcmk__xml_free_doc(res);
388  if (temp != 0) {
389  rc = EINVAL;
390  }
391  }
392 
393 done:
394  if (xslt != NULL) {
395  xsltFreeStylesheet(xslt);
396  }
397  free(sfile);
398  return rc;
399 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:245
pcmk__acl_render_how
Definition: pcmki_acl.h:15
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:974
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:388
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
Definition: schemas.c:691
#define ACL_NS_PREFIX
Definition: pcmk_acl.c:33
#define ACL_NS_Q_READABLE
Definition: pcmk_acl.c:36
#define ACL_NS_Q_DENIED
Definition: pcmk_acl.c:37
#define ACL_NS_Q_PREFIX
Definition: pcmk_acl.c:34
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:94
#define ACL_NS_Q_WRITABLE
Definition: pcmk_acl.c:35
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:458
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition: pcmki_acl.h:24
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
Wrappers for and extensions to libxml2.
void free_xml(xmlNode *child)
Definition: xml.c:958
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:258
#define PCMK_XA_VALIDATE_WITH
Definition: xml_names.h:441
#define pcmk__assert(expr)
const char * target
Definition: pcmk_fence.c:29
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:354
xmlNode * pcmk__xc_create(xmlDoc *doc, const char *content)
Definition: xml_comment.c:28
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:658
#define pcmk__mem_assert(ptr)
void pcmk__xml_free_doc(xmlDoc *doc)
Definition: xml.c:819
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2259
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:753
int pcmk__acl_annotate_permissions(const char *cred, const xmlDoc *cib_doc, xmlDoc **acl_evaled_doc)
Annotate CIB with XML namespaces indicating ACL evaluation results.
Definition: pcmk_acl.c:203