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