pacemaker  3.0.0-d8340737c4
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  validation = crm_element_value(xmlDocGetRootElement(cib_doc),
226 
228  validation) > 0) {
230  }
231 
232  target = pcmk__xml_copy(NULL, xmlDocGetRootElement((xmlDoc *) cib_doc));
233  if (target == NULL) {
234  return EINVAL;
235  }
236 
238 
239  ret = annotate_with_siblings(target);
240 
241  if (ret == pcmk_rc_ok) {
242  char *content = crm_strdup_printf("ACLs as evaluated for user %s",
243  cred);
244 
245  comment = pcmk__xc_create(target->doc, content);
246  xmlAddPrevSibling(xmlDocGetRootElement(target->doc), comment);
247  *acl_evaled_doc = target->doc;
248  free(content);
249 
250  } else {
252  }
253  return ret;
254 }
255 
256 int
257 pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
258  xmlChar **doc_txt_ptr)
259 {
260  xmlDoc *xslt_doc;
261  xsltStylesheet *xslt;
262  xsltTransformContext *xslt_ctxt;
263  xmlDoc *res;
264  char *sfile;
265  static const char *params_namespace[] = {
266  "accessrendercfg:c-writable", ACL_NS_Q_PREFIX "writable:",
267  "accessrendercfg:c-readable", ACL_NS_Q_PREFIX "readable:",
268  "accessrendercfg:c-denied", ACL_NS_Q_PREFIX "denied:",
269  "accessrendercfg:c-reset", "",
270  "accessrender:extra-spacing", "no",
271  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
272  NULL
273  }, *params_useansi[] = {
274  /* start with hard-coded defaults, then adapt per the template ones */
275  "accessrendercfg:c-writable", "\x1b[32m",
276  "accessrendercfg:c-readable", "\x1b[34m",
277  "accessrendercfg:c-denied", "\x1b[31m",
278  "accessrendercfg:c-reset", "\x1b[0m",
279  "accessrender:extra-spacing", "no",
280  "accessrender:self-reproducing-prefix", ACL_NS_Q_PREFIX,
281  NULL
282  }, *params_noansi[] = {
283  "accessrendercfg:c-writable", "vvv---[ WRITABLE ]---vvv",
284  "accessrendercfg:c-readable", "vvv---[ READABLE ]---vvv",
285  "accessrendercfg:c-denied", "vvv---[ ~DENIED~ ]---vvv",
286  "accessrendercfg:c-reset", "",
287  "accessrender:extra-spacing", "yes",
288  "accessrender:self-reproducing-prefix", "",
289  NULL
290  };
291  const char **params;
292  int rc = pcmk_rc_ok;
293  xmlParserCtxtPtr parser_ctxt;
294 
295  /* unfortunately, the input (coming from CIB originally) was parsed with
296  blanks ignored, and since the output is a conversion of XML to text
297  format (we would be covered otherwise thanks to implicit
298  pretty-printing), we need to dump the tree to string output first,
299  only to subsequently reparse it -- this time with blanks honoured */
300  xmlChar *annotated_dump;
301  int dump_size;
302 
304 
305  // Color is the default render mode for terminals; text is default otherwise
306  if (how == pcmk__acl_render_default) {
307  if (isatty(STDOUT_FILENO)) {
309  } else {
310  how = pcmk__acl_render_text;
311  }
312  }
313 
314  xmlDocDumpFormatMemory(annotated_doc, &annotated_dump, &dump_size, 1);
315 
316  /* res does not need private data: it's temporary and used only with libxslt
317  * functions
318  */
319  res = xmlReadDoc(annotated_dump, "on-the-fly-access-render", NULL,
320  XML_PARSE_NONET);
321  pcmk__assert(res != NULL);
322  xmlFree(annotated_dump);
323  pcmk__xml_free_doc(annotated_doc);
324  annotated_doc = res;
325 
327  "access-render-2");
328  parser_ctxt = xmlNewParserCtxt();
329 
330  pcmk__assert(sfile != NULL);
331  pcmk__mem_assert(parser_ctxt);
332 
333  xslt_doc = xmlCtxtReadFile(parser_ctxt, sfile, NULL, XML_PARSE_NONET);
334 
335  xslt = xsltParseStylesheetDoc(xslt_doc); /* acquires xslt_doc! */
336  if (xslt == NULL) {
337  crm_crit("Problem in parsing %s", sfile);
338  rc = EINVAL;
339  goto done;
340  }
341  xmlFreeParserCtxt(parser_ctxt);
342 
343  xslt_ctxt = xsltNewTransformContext(xslt, annotated_doc);
344  pcmk__mem_assert(xslt_ctxt);
345 
346  switch (how) {
348  params = params_namespace;
349  break;
351  params = params_noansi;
352  break;
353  default:
354  /* pcmk__acl_render_color is the only remaining option.
355  * The compiler complains about params possibly uninitialized if we
356  * don't use default here.
357  */
358  params = params_useansi;
359  break;
360  }
361 
362  xsltQuoteUserParams(xslt_ctxt, params);
363 
364  res = xsltApplyStylesheetUser(xslt, annotated_doc, NULL,
365  NULL, NULL, xslt_ctxt);
366 
367  pcmk__xml_free_doc(annotated_doc);
368  annotated_doc = NULL;
369  xsltFreeTransformContext(xslt_ctxt);
370  xslt_ctxt = NULL;
371 
372  if (how == pcmk__acl_render_color && params != params_useansi) {
373  char **param_i = (char **) params;
374  do {
375  free(*param_i);
376  } while (*param_i++ != NULL);
377  free(params);
378  }
379 
380  if (res == NULL) {
381  rc = EINVAL;
382  } else {
383  int doc_txt_len;
384  int temp = xsltSaveResultToString(doc_txt_ptr, &doc_txt_len, res, xslt);
385 
386  pcmk__xml_free_doc(res);
387  if (temp != 0) {
388  rc = EINVAL;
389  }
390  }
391 
392 done:
393  if (xslt != NULL) {
394  xsltFreeStylesheet(xslt);
395  }
396  free(sfile);
397  return rc;
398 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:213
pcmk__acl_render_how
Definition: pcmki_acl.h:19
xmlNode * pcmk__xml_copy(xmlNode *parent, xmlNode *src)
Definition: xml.c:805
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:356
int pcmk__cmp_schemas_by_name(const char *schema1_name, const char *schema2_name)
Definition: schemas.c:662
#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:96
void pcmk__xml_free(xmlNode *xml)
Definition: xml.c:789
#define ACL_NS_Q_WRITABLE
Definition: pcmk_acl.c:35
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition: pcmki_acl.h:28
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: xml_element.c:1168
Wrappers for and extensions to libxml2.
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:257
#define PCMK_XA_VALIDATE_WITH
Definition: xml_names.h:441
#define pcmk__assert(expr)
const char * target
Definition: pcmk_fence.c:31
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:334
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:655
#define pcmk__mem_assert(ptr)
void pcmk__xml_free_doc(xmlDoc *doc)
Definition: xml.c:495
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:1520
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:750
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
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1