pacemaker 2.1.4-dc6eb4362e
Scalable High-Availability cluster resource manager
pcmk_acl.c
Go to the documentation of this file.
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>
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, xmlNs **ns_recycle_writable, xmlNs **ns_recycle_readable, xmlNs **ns_recycle_denied)
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
104static int
105pcmk__acl_annotate_permissions_recursive(xmlNode *xml_modify)
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:
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,
151 ns = NS_DENIED;
152 } else if (!pcmk__check_acl(i_node,
153 (const char *) i_node->name,
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
185int
186pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc,
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),
209 version = get_schema_version(validation);
212 }
213
214 target = copy_xml(xmlDocGetRootElement(cib_doc));
215 if (target == NULL) {
216 return EINVAL;
217 }
218
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
240int
241pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how,
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
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}
bool pcmk_acl_required(const char *user)
Check whether ACLs are required for a given user.
Definition: acl.c:683
bool pcmk__check_acl(xmlNode *xml, const char *name, enum xml_private_flags mode)
Definition: acl.c:611
void pcmk__enable_acl(xmlNode *acl_source, xmlNode *target, const char *user)
Definition: acl.c:338
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:357
#define CRM_CHECK(expr, failure_action)
Definition: logging.h:226
#define XML_ATTR_VALIDATION
Definition: msg_xml.h:120
const char * crm_element_value(const xmlNode *data, const char *name)
Retrieve the value of an XML attribute.
Definition: nvpair.c:529
#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
#define ACL_NS_Q_PREFIX
Definition: pcmk_acl.c:35
#define ACL_NS_Q_READABLE
Definition: pcmk_acl.c:37
int pcmk__acl_annotate_permissions(const char *cred, xmlDoc *cib_doc, xmlDoc **acl_evaled_doc)
Mark CIB with namespace-encoded result of ACLs eval'd per credential.
Definition: pcmk_acl.c:186
int pcmk__acl_evaled_render(xmlDoc *annotated_doc, enum pcmk__acl_render_how how, xmlChar **doc_txt_ptr)
Definition: pcmk_acl.c:241
const char * target
Definition: pcmk_fence.c:28
pcmk__acl_render_how
Definition: pcmki_acl.h:20
@ pcmk__acl_render_text
Definition: pcmki_acl.h:22
@ pcmk__acl_render_color
Definition: pcmki_acl.h:23
@ pcmk__acl_render_namespace
Definition: pcmki_acl.h:21
#define PCMK__COMPAT_ACL_2_MIN_INCL
Definition: pcmki_acl.h:33
#define CRM_ASSERT(expr)
Definition: results.h:42
@ pcmk_rc_ok
Definition: results.h:146
@ pcmk_rc_schema_validation
Definition: results.h:126
@ pcmk_rc_already
Definition: results.h:138
Wrappers for and extensions to libxml2.
int get_schema_version(const char *name)
Definition: schemas.c:1024
const xmlChar * pcmkXmlStr
Definition: xml.h:52
xmlNode * copy_xml(xmlNode *src_node)
Definition: xml.c:830
void pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag)
Definition: xml.c:117
@ pcmk__xf_acl_write
Definition: xml_internal.h:329
@ pcmk__xf_tracking
Definition: xml_internal.h:322
@ pcmk__xf_acl_read
Definition: xml_internal.h:328
@ pcmk__xml_artefact_ns_base_xslt
Definition: xml_internal.h:153
char * pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec)
Definition: xml.c:2934