pacemaker  2.1.7-0f7f88312f
Scalable High-Availability cluster resource manager
pcmk_acl.c
Go to the documentation of this file.
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>
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 
55 static void
56 pcmk__acl_mark_node_with_namespace(xmlNode *i_node, const xmlChar *ns, int *ret,
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 
109 static int
110 annotate_with_siblings(xmlNode *xml_modify)
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:
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,
162  ns = NS_DENIED;
163  } else if (!pcmk__check_acl(i_node,
164  (const char *) i_node->name,
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,
181  ns = NS_DENIED;
182  } else if (!pcmk__check_acl(i_node->parent,
183  (const char *) i_node->name,
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,
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),
229  version = get_schema_version(validation);
232  }
233 
234  target = copy_xml(xmlDocGetRootElement((xmlDoc *) cib_doc));
235  if (target == NULL) {
236  return EINVAL;
237  }
238 
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,
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 
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)) {
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 
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) {
351  params = params_namespace;
352  break;
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 }
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:238
pcmk__acl_render_how
Definition: pcmki_acl.h:15
A dumping ground.
#define crm_crit(fmt, args...)
Definition: logging.h:380
#define ACL_NS_PREFIX
Definition: pcmk_acl.c:34
#define ACL_NS_Q_READABLE
Definition: pcmk_acl.c:37
#define ACL_NS_Q_DENIED
Definition: pcmk_acl.c:38
int get_schema_version(const char *name)
Definition: schemas.c:967
#define ACL_NS_Q_PREFIX
Definition: pcmk_acl.c:35
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:78
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:789
#define ACL_NS_Q_WRITABLE
Definition: pcmk_acl.c:36
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:447
#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.
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:142
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:263
const xmlChar * pcmkXmlStr
Definition: xml.h:50
const char * target
Definition: pcmk_fence.c:29
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:344
#define CRM_ASSERT(expr)
Definition: results.h:42
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:647
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2614
uint32_t version
Definition: remote.c:213
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:743
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:204