xref: /unit/src/test/nxt_clone_test.c (revision 1439:32578e837322)
1 /*
2  * Copyright (C) NGINX, Inc.
3  * Copyright (C) Valentin V. Bartenev
4  */
5 
6 #include <nxt_main.h>
7 #include <nxt_conf.h>
8 #include "nxt_tests.h"
9 
10 
11 #define UIDMAP 1
12 #define GIDMAP 2
13 
14 
15 typedef struct {
16     nxt_int_t         map_type;
17     nxt_str_t         map_data;
18     nxt_int_t         setid;
19     nxt_credential_t  creds;
20     nxt_uid_t         unit_euid;
21     nxt_gid_t         unit_egid;
22     nxt_int_t         result;
23     nxt_str_t         errmsg;
24 } nxt_clone_creds_testcase_t;
25 
26 typedef struct {
27     nxt_clone_creds_testcase_t  *tc;
28 } nxt_clone_creds_ctx_t;
29 
30 
31 nxt_int_t nxt_clone_test_mappings(nxt_task_t *task, nxt_mp_t *mp,
32     nxt_clone_creds_ctx_t *ctx, nxt_clone_creds_testcase_t *tc);
33 void nxt_cdecl nxt_clone_test_log_handler(nxt_uint_t level, nxt_log_t *log,
34     const char *fmt, ...);
35 nxt_int_t nxt_clone_test_map_assert(nxt_task_t *task,
36     nxt_clone_creds_testcase_t *tc, nxt_clone_credential_map_t *map);
37 static nxt_int_t nxt_clone_test_parse_map(nxt_task_t *task,
38     nxt_str_t *map_str, nxt_clone_credential_map_t *map);
39 
40 
41 nxt_log_t *test_log;
42 
43 static nxt_gid_t gids[] = {1000, 10000, 60000};
44 
45 static nxt_clone_creds_testcase_t testcases[] = {
46     {
47         /*
48          * Unprivileged unit
49          *
50          * if no uid mapping and app creds and unit creds are the same,
51          * then we automatically add a map for the creds->uid.
52          * Then, child process can safely setuid(creds->uid) in
53          * the new namespace.
54          */
55         UIDMAP,
56         nxt_string(""),
57         0,
58         {"nobody", 65534, 65534, 0, NULL},
59         1000, 1000,
60         NXT_OK,
61         nxt_string("")
62     },
63     {
64         UIDMAP,
65         nxt_string(""),
66         0,
67         {"johndoe", 10000, 10000, 0, NULL},
68         1000, 1000,
69         NXT_OK,
70         nxt_string("")
71     },
72     {
73         UIDMAP,
74         nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 1}]"),
75         0,
76         {"johndoe", 1000, 1000, 0, NULL},
77         1000, 1000,
78         NXT_OK,
79         nxt_string("")
80     },
81     {
82         UIDMAP,
83         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
84         0,
85         {"root", 0, 0, 0, NULL},
86         1000, 1000,
87         NXT_OK,
88         nxt_string("")
89     },
90     {
91         UIDMAP,
92         nxt_string("[{\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
93         0,
94         {"nobody", 65534, 0, 0, NULL},
95         1000, 1000,
96         NXT_OK,
97         nxt_string("")
98     },
99     {
100         UIDMAP,
101         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
102                    " {\"container\": 1000, \"host\": 2000, \"size\": 1}]"),
103         0,
104         {"root", 0, 0, 0, NULL},
105         1000, 1000,
106         NXT_ERROR,
107         nxt_string("\"uidmap\" field has 2 entries but unprivileged unit has "
108                    "a maximum of 1 map.")
109     },
110     {
111         UIDMAP,
112         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
113                    " {\"container\": 1000, \"host\": 2000, \"size\": 1}]"),
114         1, /* privileged */
115         {"root", 0, 0, 0, NULL},
116         1000, 1000,
117         NXT_OK,
118         nxt_string("")
119     },
120     {
121         UIDMAP,
122         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
123                    " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
124         1, /* privileged */
125         {"johndoe", 500, 0, 0, NULL},
126         1000, 1000,
127         NXT_OK,
128         nxt_string("")
129     },
130     {
131         UIDMAP,
132         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
133                    " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
134         1, /* privileged */
135         {"johndoe", 1000, 0, 0, NULL},
136         1000, 1000,
137         NXT_OK,
138         nxt_string("")
139     },
140     {
141         UIDMAP,
142         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
143                    " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
144         1, /* privileged */
145         {"johndoe", 1500, 0, 0, NULL},
146         1000, 1000,
147         NXT_OK,
148         nxt_string("")
149     },
150     {
151         UIDMAP,
152         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
153                    " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
154         1, /* privileged */
155         {"johndoe", 1999, 0, 0, NULL},
156         1000, 1000,
157         NXT_OK,
158         nxt_string("")
159     },
160     {
161         UIDMAP,
162         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
163                    " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
164         1, /* privileged */
165         {"johndoe", 2000, 0, 0, NULL},
166         1000, 1000,
167         NXT_ERROR,
168         nxt_string("\"uidmap\" field has no \"container\" entry for user "
169                    "\"johndoe\" (uid 2000)")
170     },
171     {
172         /*
173          * Unprivileged unit
174          *
175          * if no gid mapping and app creds and unit creds are the same,
176          * then we automatically add a map for the creds->base_gid.
177          * Then, child process can safely setgid(creds->base_gid) in
178          * the new namespace.
179          */
180         GIDMAP,
181         nxt_string("[]"),
182         0,
183         {"nobody", 65534, 65534, 0, NULL},
184         1000, 1000,
185         NXT_OK,
186         nxt_string("")
187     },
188     {
189         /*
190          * Unprivileged unit
191          *
192          * Inside the new namespace, we can have any gid but it
193          * should map to parent gid (in this case 1000) in parent
194          * namespace.
195          */
196         GIDMAP,
197         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
198         0,
199         {"root", 0, 0, 0, NULL},
200         1000, 1000,
201         NXT_OK,
202         nxt_string("")
203     },
204     {
205         GIDMAP,
206         nxt_string("[{\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
207         0,
208         {"nobody", 65534, 65534, 0, NULL},
209         1000, 1000,
210         NXT_OK,
211         nxt_string("")
212     },
213     {
214         /*
215          * Unprivileged unit
216          *
217          * There's no mapping for "johndoe" (gid 1000) inside the namespace.
218          */
219         GIDMAP,
220         nxt_string("[{\"container\": 65535, \"host\": 1000, \"size\": 1}]"),
221         0,
222         {"johndoe", 1000, 1000, 0, NULL},
223         1000, 1000,
224         NXT_ERROR,
225         nxt_string("\"gidmap\" field has no \"container\" entry for "
226                     "gid 1000.")
227     },
228     {
229         GIDMAP,
230         nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 2}]"),
231         0,
232         {"johndoe", 1000, 1000, 0, NULL},
233         1000, 1000,
234         NXT_ERROR,
235         nxt_string("\"gidmap\" field has an entry with \"size\": 2, but "
236                     "for unprivileged unit it must be 1.")
237     },
238     {
239         GIDMAP,
240         nxt_string("[{\"container\": 1000, \"host\": 1001, \"size\": 1}]"),
241         0,
242         {"johndoe", 1000, 1000, 0, NULL},
243         1000, 1000,
244         NXT_ERROR,
245         nxt_string("\"gidmap\" field has an entry for host gid 1001 but "
246                     "unprivileged unit can only map itself (gid 1000) "
247                     "into child namespaces.")
248     },
249     {
250         GIDMAP,
251         nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 1}]"),
252         0,
253         {"johndoe", 1000, 1000, 3, gids},
254         1000, 1000,
255         NXT_ERROR,
256         nxt_string("unprivileged unit disallow supplementary groups for "
257                     "new namespace (user \"johndoe\" has 3 groups).")
258     },
259 
260     /* privileged unit */
261 
262     /* not root with capabilities */
263     {
264         GIDMAP,
265         nxt_string("[]"),
266         1,
267         {"johndoe", 1000, 1000, 0, NULL},
268         1000, 1000,
269         NXT_OK,
270         nxt_string("")
271     },
272     {
273         GIDMAP,
274         nxt_string(""),
275         1,
276         {"johndoe", 1000, 1000, 0, NULL},
277         1000, 1000,
278         NXT_OK,
279         nxt_string("")
280     },
281     {
282         /* missing gid of {"user": "nobody"} */
283         GIDMAP,
284         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
285         1,
286         {"nobody", 65534, 65534, 0, NULL},
287         1000, 1000,
288         NXT_ERROR,
289         nxt_string("\"gidmap\" field has no \"container\" entry for "
290                     "gid 65534.")
291     },
292     {
293         /* solves the previous by mapping 65534 gids */
294         GIDMAP,
295         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 65535}]"),
296         1,
297         {"nobody", 65534, 65534, 0, NULL},
298         1000, 1000,
299         NXT_OK,
300         nxt_string("")
301     },
302     {
303         /* solves by adding a separate mapping */
304         GIDMAP,
305         nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
306                    " {\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
307         1,
308         {"nobody", 65534, 65534, 0, NULL},
309         1000, 1000,
310         NXT_OK,
311         nxt_string("")
312     },
313     {
314         /*
315          * Map a big range
316          */
317         GIDMAP,
318         nxt_string("[{\"container\": 0, \"host\": 0, \"size\": 200000}]"),
319         1,
320         {"johndoe", 100000, 100000, 0, NULL},
321         1000, 1000,
322         NXT_OK,
323         nxt_string("")
324     },
325     {
326         /*
327          * Validate if supplementary groups are mapped
328          */
329         GIDMAP,
330         nxt_string("[]"),
331         1,
332         {"johndoe", 1000, 1000, 3, gids},
333         1000, 1000,
334         NXT_ERROR,
335         nxt_string("\"gidmap\" field has no entries but user \"johndoe\" "
336                    "has 3 suplementary groups."),
337     },
338     {
339         GIDMAP,
340         nxt_string("[{\"container\": 0, \"host\": 0, \"size\": 1}]"),
341         1,
342         {"johndoe", 1000, 1000, 3, gids},
343         1000, 1000,
344         NXT_ERROR,
345         nxt_string("\"gidmap\" field has no \"container\" entry for "
346                    "gid 1000."),
347     },
348     {
349         GIDMAP,
350         nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1}]"),
351         1,
352         {"johndoe", 1000, 1000, 3, gids},
353         1000, 1000,
354         NXT_ERROR,
355         nxt_string("\"gidmap\" field has missing suplementary gid mappings "
356                    "(found 1 out of 3)."),
357     },
358     {
359         GIDMAP,
360         nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1},"
361                    " {\"container\": 10000, \"host\": 10000, \"size\": 1}]"),
362         1,
363         {"johndoe", 1000, 1000, 3, gids},
364         1000, 1000,
365         NXT_ERROR,
366         nxt_string("\"gidmap\" field has missing suplementary gid mappings "
367                    "(found 2 out of 3)."),
368     },
369     {
370         /*
371          * Fix all mappings
372          */
373         GIDMAP,
374         nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1},"
375                    "{\"container\": 10000, \"host\": 10000, \"size\": 1},"
376                    " {\"container\": 60000, \"host\": 60000, \"size\": 1}]"),
377         1,
378         {"johndoe", 1000, 1000, 3, gids},
379         1000, 1000,
380         NXT_OK,
381         nxt_string(""),
382     },
383 };
384 
385 
386 void nxt_cdecl
nxt_clone_test_log_handler(nxt_uint_t level,nxt_log_t * log,const char * fmt,...)387 nxt_clone_test_log_handler(nxt_uint_t level, nxt_log_t *log,
388     const char *fmt, ...)
389 {
390     u_char                      *p, *end;
391     va_list                     args;
392     nxt_clone_creds_ctx_t       *ctx;
393     nxt_clone_creds_testcase_t  *tc;
394     u_char                      msg[NXT_MAX_ERROR_STR];
395 
396     p = msg;
397     end = msg + NXT_MAX_ERROR_STR;
398 
399     ctx = log->ctx;
400     tc = ctx->tc;
401 
402     va_start(args, fmt);
403     p = nxt_vsprintf(p, end, fmt, args);
404     va_end(args);
405 
406     *p++ = '\0';
407 
408     if (tc->result == NXT_OK && level == NXT_LOG_DEBUG) {
409         return;
410     }
411 
412     if (tc->errmsg.length == 0) {
413         nxt_log_error(NXT_LOG_ERR, &nxt_main_log, "unexpected log: %s", msg);
414         return;
415     }
416 
417     if (!nxt_str_eq(&tc->errmsg, msg, (nxt_uint_t) (p - msg - 1))) {
418         nxt_log_error(NXT_LOG_ERR, &nxt_main_log,
419                       "error log mismatch: got [%s] but wants [%V]",
420                       msg, &tc->errmsg);
421         return;
422     }
423 }
424 
425 
426 nxt_int_t
nxt_clone_creds_test(nxt_thread_t * thr)427 nxt_clone_creds_test(nxt_thread_t *thr)
428 {
429     nxt_mp_t               *mp;
430     nxt_int_t              ret;
431     nxt_uint_t             count, i;
432     nxt_task_t             *task;
433     nxt_runtime_t          rt;
434     nxt_clone_creds_ctx_t  ctx;
435 
436     nxt_log_t nxt_clone_creds_log = {
437         NXT_LOG_INFO,
438         0,
439         nxt_clone_test_log_handler,
440         NULL,
441         &ctx
442     };
443 
444     nxt_thread_time_update(thr);
445 
446     thr->runtime = &rt;
447 
448     task = thr->task;
449 
450     mp = nxt_mp_create(1024, 128, 256, 32);
451     if (mp == NULL) {
452         return NXT_ERROR;
453     }
454 
455     rt.mem_pool = mp;
456 
457     test_log = task->log;
458     task->log = &nxt_clone_creds_log;
459     task->thread = thr;
460 
461     count = sizeof(testcases)/sizeof(nxt_clone_creds_testcase_t);
462 
463     for (i = 0; i < count; i++) {
464         ret = nxt_clone_test_mappings(task, mp, &ctx, &testcases[i]);
465 
466         if (ret != NXT_OK) {
467             goto fail;
468         }
469     }
470 
471     ret = NXT_OK;
472 
473     nxt_log_error(NXT_LOG_NOTICE, test_log, "clone creds test passed");
474 
475 fail:
476     task->log = test_log;
477     nxt_mp_destroy(mp);
478 
479     return ret;
480 }
481 
482 
483 nxt_int_t
nxt_clone_test_mappings(nxt_task_t * task,nxt_mp_t * mp,nxt_clone_creds_ctx_t * ctx,nxt_clone_creds_testcase_t * tc)484 nxt_clone_test_mappings(nxt_task_t *task, nxt_mp_t *mp,
485     nxt_clone_creds_ctx_t *ctx, nxt_clone_creds_testcase_t *tc)
486 {
487     nxt_int_t                   ret;
488     nxt_runtime_t               *rt;
489     nxt_clone_credential_map_t  map;
490 
491     rt = task->thread->runtime;
492 
493     map.size = 0;
494 
495     if (tc->map_data.length > 0) {
496         ret = nxt_clone_test_parse_map(task, &tc->map_data, &map);
497         if (ret != NXT_OK) {
498             return NXT_ERROR;
499         }
500     }
501 
502     rt->capabilities.setid = tc->setid;
503 
504     nxt_euid = tc->unit_euid;
505     nxt_egid = tc->unit_egid;
506 
507     ctx->tc = tc;
508 
509     if (nxt_clone_test_map_assert(task, tc, &map) != NXT_OK) {
510         return NXT_ERROR;
511     }
512 
513     if (tc->setid && nxt_euid != 0) {
514         /*
515          * Running as root should have the same behavior as
516          * passing Linux capabilities.
517          */
518 
519         nxt_euid = 0;
520         nxt_egid = 0;
521 
522         if (nxt_clone_test_map_assert(task, tc, &map) != NXT_OK) {
523             return NXT_ERROR;
524         }
525     }
526 
527     return NXT_OK;
528 }
529 
530 
531 nxt_int_t
nxt_clone_test_map_assert(nxt_task_t * task,nxt_clone_creds_testcase_t * tc,nxt_clone_credential_map_t * map)532 nxt_clone_test_map_assert(nxt_task_t *task, nxt_clone_creds_testcase_t *tc,
533     nxt_clone_credential_map_t *map)
534 {
535     nxt_int_t ret;
536 
537     if (tc->map_type == UIDMAP) {
538         ret = nxt_clone_vldt_credential_uidmap(task, map, &tc->creds);
539     } else {
540         ret = nxt_clone_vldt_credential_gidmap(task, map, &tc->creds);
541     }
542 
543     if (ret != tc->result) {
544         nxt_log_error(NXT_LOG_ERR, &nxt_main_log,
545                       "return %d instead of %d (map: %V)", ret, tc->result,
546                       &tc->map_data);
547 
548         return NXT_ERROR;
549     }
550 
551     return NXT_OK;
552 }
553 
554 
555 static nxt_int_t
nxt_clone_test_parse_map(nxt_task_t * task,nxt_str_t * map_str,nxt_clone_credential_map_t * map)556 nxt_clone_test_parse_map(nxt_task_t *task, nxt_str_t *map_str,
557     nxt_clone_credential_map_t *map)
558 {
559     nxt_uint_t        i;
560     nxt_runtime_t     *rt;
561     nxt_conf_value_t  *array, *obj, *value;
562 
563     static nxt_str_t  host_name = nxt_string("host");
564     static nxt_str_t  cont_name = nxt_string("container");
565     static nxt_str_t  size_name = nxt_string("size");
566 
567     rt = task->thread->runtime;
568 
569     array = nxt_conf_json_parse_str(rt->mem_pool, map_str);
570     if (array == NULL) {
571         return NXT_ERROR;
572     }
573 
574     map->size = nxt_conf_array_elements_count(array);
575 
576     if (map->size == 0) {
577         return NXT_OK;
578     }
579 
580     map->map = nxt_mp_alloc(rt->mem_pool,
581                             map->size * sizeof(nxt_clone_map_entry_t));
582 
583     if (map->map == NULL) {
584         return NXT_ERROR;
585     }
586 
587     for (i = 0; i < map->size; i++) {
588         obj = nxt_conf_get_array_element(array, i);
589 
590         value = nxt_conf_get_object_member(obj, &host_name, NULL);
591         map->map[i].host = nxt_conf_get_number(value);
592 
593         value = nxt_conf_get_object_member(obj, &cont_name, NULL);
594         map->map[i].container = nxt_conf_get_number(value);
595 
596         value = nxt_conf_get_object_member(obj, &size_name, NULL);
597         map->map[i].size = nxt_conf_get_number(value);
598     }
599 
600     return NXT_OK;
601 }
602