pacemaker 2.1.7-2.1.7
Scalable High-Availability cluster resource manager
Loading...
Searching...
No Matches
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
40static const xmlChar *NS_WRITABLE = (const xmlChar *) ACL_NS_PREFIX "writable";
41static const xmlChar *NS_READABLE = (const xmlChar *) ACL_NS_PREFIX "readable";
42static const xmlChar *NS_DENIED = (const xmlChar *) ACL_NS_PREFIX "denied";
43
55static void
56pcmk__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
109static int
110annotate_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
203int
204pcmk__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
262int
263pcmk__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 {
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}
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition acl.c:743
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition acl.c:647
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition acl.c:344
uint32_t version
Definition remote.c:1
char * crm_strdup_printf(char const *format,...) G_GNUC_PRINTF(1
A dumping ground.
#define crm_crit(fmt, args...)
Definition logging.h:378
#define CRM_CHECK(expr, failure_action)
Definition logging.h:238
#define XML_ATTR_VALIDATION
Definition msg_xml.h:142
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition nvpair.c:447
#define ACL_NS_Q_WRITABLE
Definition pcmk_acl.c:36
#define ACL_NS_PREFIX
Definition pcmk_acl.c:34
#define ACL_NS_Q_DENIED
Definition pcmk_acl.c:38
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
#define ACL_NS_Q_PREFIX
Definition pcmk_acl.c:35
#define ACL_NS_Q_READABLE
Definition pcmk_acl.c:37
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition pcmk_acl.c:263
const char * target
Definition pcmk_fence.c:29
pcmk__acl_render_how
Definition pcmki_acl.h:15
@ pcmk__acl_render_text
Definition pcmki_acl.h:18
@ pcmk__acl_render_color
Definition pcmki_acl.h:19
@ pcmk__acl_render_namespace
Definition pcmki_acl.h:17
@ pcmk__acl_render_none
Definition pcmki_acl.h:16
@ pcmk__acl_render_default
Definition pcmki_acl.h:20
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition pcmki_acl.h:24
#define CRM_ASSERT(expr)
Definition results.h:42
@ pcmk_rc_ok
Definition results.h:154
@ pcmk_rc_schema_validation
Definition results.h:134
@ pcmk_rc_already
Definition results.h:146
Wrappers for and extensions to libxml2.
int get_schema_version(const char *name)
Definition schemas.c:967
const xmlChar * pcmkXmlStr
Definition xml.h:50
xmlNode * copy_xml(xmlNode *src_node)
Definition xml.c:789
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition xml.c:78
@ pcmk__xf_acl_write
@ pcmk__xf_tracking
@ pcmk__xf_acl_read
@ pcmk__xml_artefact_ns_base_xslt
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition xml.c:2614