summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--auto/isolation5
-rw-r--r--auto/sources6
-rw-r--r--src/nxt_clone.c311
-rw-r--r--src/nxt_clone.h40
-rw-r--r--src/nxt_credential.c66
-rw-r--r--src/nxt_credential.h4
-rw-r--r--src/nxt_lib.c2
-rw-r--r--src/nxt_main_process.c213
-rw-r--r--src/nxt_process.c32
-rw-r--r--src/nxt_process.h18
-rw-r--r--src/test/nxt_clone_test.c601
-rw-r--r--src/test/nxt_tests.c6
-rw-r--r--src/test/nxt_tests.h1
13 files changed, 1170 insertions, 135 deletions
diff --git a/auto/isolation b/auto/isolation
index c26a4991..d231de12 100644
--- a/auto/isolation
+++ b/auto/isolation
@@ -5,6 +5,7 @@
NXT_ISOLATION=NO
NXT_HAVE_CLONE=NO
+NXT_HAVE_CLONE_NEWUSER=NO
nsflags="USER NS PID NET UTS CGROUP"
@@ -42,6 +43,10 @@ if [ $nxt_found = yes ]; then
. auto/feature
if [ $nxt_found = yes ]; then
+ if [ $flag = "USER" ]; then
+ NXT_HAVE_CLONE_NEWUSER=YES
+ fi
+
if [ "$NXT_ISOLATION" = "NO" ]; then
NXT_ISOLATION=$flag
else
diff --git a/auto/sources b/auto/sources
index 33c1dcd4..70d6eb8d 100644
--- a/auto/sources
+++ b/auto/sources
@@ -163,6 +163,12 @@ NXT_TEST_SRCS=" \
src/test/nxt_strverscmp_test.c \
"
+
+if [ $NXT_HAVE_CLONE_NEWUSER = YES ]; then
+ NXT_TEST_SRCS="$NXT_TEST_SRCS src/test/nxt_clone_test.c"
+fi
+
+
NXT_LIB_UTF8_FILE_NAME_TEST_SRCS=" \
src/test/nxt_utf8_file_name_test.c \
"
diff --git a/src/nxt_clone.c b/src/nxt_clone.c
index a2c376a3..9ee3c012 100644
--- a/src/nxt_clone.c
+++ b/src/nxt_clone.c
@@ -25,26 +25,18 @@ nxt_clone(nxt_int_t flags)
#if (NXT_HAVE_CLONE_NEWUSER)
-/* map uid 65534 to unit pid */
-#define NXT_DEFAULT_UNPRIV_MAP "65534 %d 1"
-
-nxt_int_t nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid,
+nxt_int_t nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
const char *str);
-nxt_int_t nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile,
- pid_t pid, nxt_int_t defval, nxt_conf_value_t *mapobj);
-nxt_int_t nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile,
+nxt_int_t nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile,
+ pid_t pid, nxt_int_t default_container, nxt_int_t default_host,
+ nxt_clone_credential_map_t *map);
+nxt_int_t nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
pid_t pid, u_char *mapinfo);
-typedef struct {
- nxt_int_t container;
- nxt_int_t host;
- nxt_int_t size;
-} nxt_clone_procmap_t;
-
-
nxt_int_t
-nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid, const char *str)
+nxt_clone_credential_setgroups(nxt_task_t *task, pid_t child_pid,
+ const char *str)
{
int fd, n;
u_char *p, *end;
@@ -89,8 +81,8 @@ nxt_clone_proc_setgroups(nxt_task_t *task, pid_t child_pid, const char *str)
nxt_int_t
-nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile, pid_t pid,
- u_char *mapinfo)
+nxt_clone_credential_map_write(nxt_task_t *task, const char *mapfile,
+ pid_t pid, u_char *mapinfo)
{
int len, mapfd;
u_char *p, *end;
@@ -139,17 +131,13 @@ nxt_clone_proc_map_write(nxt_task_t *task, const char *mapfile, pid_t pid,
nxt_int_t
-nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
- nxt_int_t defval, nxt_conf_value_t *mapobj)
+nxt_clone_credential_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
+ nxt_int_t default_container, nxt_int_t default_host,
+ nxt_clone_credential_map_t *map)
{
- u_char *p, *end, *mapinfo;
- nxt_int_t container, host, size;
- nxt_int_t ret, len, count, i;
- nxt_conf_value_t *obj, *value;
-
- static nxt_str_t str_cont = nxt_string("container");
- static nxt_str_t str_host = nxt_string("host");
- static nxt_str_t str_size = nxt_string("size");
+ u_char *p, *end, *mapinfo;
+ nxt_int_t ret, len;
+ nxt_uint_t i;
/*
* uid_map one-entry size:
@@ -157,44 +145,28 @@ nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
*/
len = sizeof(u_char) * (10 + 10 + 10 + 2 + 1);
- if (mapobj != NULL) {
- count = nxt_conf_array_elements_count(mapobj);
-
- if (count == 0) {
- goto default_map;
- }
-
- len = len * count + 1;
+ if (map->size > 0) {
+ len = len * map->size + 1;
mapinfo = nxt_malloc(len);
if (nxt_slow_path(mapinfo == NULL)) {
- nxt_alert(task, "failed to allocate uid_map buffer");
return NXT_ERROR;
}
p = mapinfo;
end = mapinfo + len;
- for (i = 0; i < count; i++) {
- obj = nxt_conf_get_array_element(mapobj, i);
+ for (i = 0; i < map->size; i++) {
+ p = nxt_sprintf(p, end, "%d %d %d", map->map[i].container,
+ map->map[i].host, map->map[i].size);
- value = nxt_conf_get_object_member(obj, &str_cont, NULL);
- container = nxt_conf_get_integer(value);
-
- value = nxt_conf_get_object_member(obj, &str_host, NULL);
- host = nxt_conf_get_integer(value);
-
- value = nxt_conf_get_object_member(obj, &str_size, NULL);
- size = nxt_conf_get_integer(value);
-
- p = nxt_sprintf(p, end, "%d %d %d", container, host, size);
if (nxt_slow_path(p == end)) {
- nxt_alert(task, "write past the uid_map buffer");
+ nxt_alert(task, "write past the mapinfo buffer");
nxt_free(mapinfo);
return NXT_ERROR;
}
- if (i+1 < count) {
+ if (i+1 < map->size) {
*p++ = '\n';
} else {
@@ -203,27 +175,24 @@ nxt_clone_proc_map_set(nxt_task_t *task, const char* mapfile, pid_t pid,
}
} else {
-
-default_map:
-
mapinfo = nxt_malloc(len);
if (nxt_slow_path(mapinfo == NULL)) {
- nxt_alert(task, "failed to allocate uid_map buffer");
return NXT_ERROR;
}
end = mapinfo + len;
- p = nxt_sprintf(mapinfo, end, NXT_DEFAULT_UNPRIV_MAP, defval);
+ p = nxt_sprintf(mapinfo, end, "%d %d 1",
+ default_container, default_host);
*p = '\0';
if (nxt_slow_path(p == end)) {
- nxt_alert(task, "write past the %s buffer", mapfile);
+ nxt_alert(task, "write past mapinfo buffer");
nxt_free(mapinfo);
return NXT_ERROR;
}
}
- ret = nxt_clone_proc_map_write(task, mapfile, pid, mapinfo);
+ ret = nxt_clone_credential_map_write(task, mapfile, pid, mapinfo);
nxt_free(mapinfo);
@@ -232,31 +201,50 @@ default_map:
nxt_int_t
-nxt_clone_proc_map(nxt_task_t *task, pid_t pid, nxt_process_clone_t *clone)
+nxt_clone_credential_map(nxt_task_t *task, pid_t pid,
+ nxt_credential_t *app_creds, nxt_clone_t *clone)
{
nxt_int_t ret;
- nxt_int_t uid, gid;
+ nxt_int_t default_host_uid;
+ nxt_int_t default_host_gid;
const char *rule;
nxt_runtime_t *rt;
rt = task->thread->runtime;
- uid = geteuid();
- gid = getegid();
- rule = rt->capabilities.setid ? "allow" : "deny";
+ if (rt->capabilities.setid) {
+ rule = "allow";
+
+ /*
+ * By default we don't map a privileged user
+ */
+ default_host_uid = app_creds->uid;
+ default_host_gid = app_creds->base_gid;
+ } else {
+ rule = "deny";
+
+ default_host_uid = nxt_euid;
+ default_host_gid = nxt_egid;
+ }
+
+ ret = nxt_clone_credential_map_set(task, "uid_map", pid, app_creds->uid,
+ default_host_uid,
+ &clone->uidmap);
- ret = nxt_clone_proc_map_set(task, "uid_map", pid, uid, clone->uidmap);
if (nxt_slow_path(ret != NXT_OK)) {
return NXT_ERROR;
}
- ret = nxt_clone_proc_setgroups(task, pid, rule);
+ ret = nxt_clone_credential_setgroups(task, pid, rule);
if (nxt_slow_path(ret != NXT_OK)) {
nxt_alert(task, "failed to write /proc/%d/setgroups", pid);
return NXT_ERROR;
}
- ret = nxt_clone_proc_map_set(task, "gid_map", pid, gid, clone->gidmap);
+ ret = nxt_clone_credential_map_set(task, "gid_map", pid, app_creds->base_gid,
+ default_host_gid,
+ &clone->gidmap);
+
if (nxt_slow_path(ret != NXT_OK)) {
return NXT_ERROR;
}
@@ -264,4 +252,197 @@ nxt_clone_proc_map(nxt_task_t *task, pid_t pid, nxt_process_clone_t *clone)
return NXT_OK;
}
+
+nxt_int_t
+nxt_clone_vldt_credential_uidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds)
+{
+ nxt_int_t id;
+ nxt_uint_t i;
+ nxt_runtime_t *rt;
+ nxt_clone_map_entry_t m;
+
+ if (map->size == 0) {
+ return NXT_OK;
+ }
+
+ rt = task->thread->runtime;
+
+ if (!rt->capabilities.setid) {
+ if (nxt_slow_path(map->size > 1)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has %d entries "
+ "but unprivileged unit has a maximum of 1 map.",
+ map->size);
+
+ return NXT_ERROR;
+ }
+
+ id = map->map[0].host;
+
+ if (nxt_slow_path((nxt_uid_t) id != nxt_euid)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has an entry for "
+ "host uid %d but unprivileged unit can only map itself "
+ "(uid %d) into child namespaces.", id, nxt_euid);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ for (i = 0; i < map->size; i++) {
+ m = map->map[i];
+
+ if (creds->uid >= (nxt_uid_t) m.container
+ && creds->uid < (nxt_uid_t) (m.container + m.size))
+ {
+ return NXT_OK;
+ }
+ }
+
+ nxt_log(task, NXT_LOG_NOTICE, "\"uidmap\" field has no \"container\" "
+ "entry for user \"%s\" (uid %d)", creds->user, creds->uid);
+
+ return NXT_ERROR;
+}
+
+
+nxt_int_t
+nxt_clone_vldt_credential_gidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds)
+{
+ nxt_uint_t base_ok, gid_ok, gids_ok;
+ nxt_uint_t i, j;
+ nxt_runtime_t *rt;
+ nxt_clone_map_entry_t m;
+
+ rt = task->thread->runtime;
+
+ if (!rt->capabilities.setid) {
+ if (creds->ngroups > 0
+ && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid)) {
+ nxt_log(task, NXT_LOG_NOTICE,
+ "unprivileged unit disallow supplementary groups for "
+ "new namespace (user \"%s\" has %d group%s).",
+ creds->user, creds->ngroups,
+ creds->ngroups > 1 ? "s" : "");
+
+ return NXT_ERROR;
+ }
+
+ if (map->size == 0) {
+ return NXT_OK;
+ }
+
+ if (nxt_slow_path(map->size > 1)) {
+ nxt_log(task, NXT_LOG_NOTICE, "\"gidmap\" field has %d entries "
+ "but unprivileged unit has a maximum of 1 map.",
+ map->size);
+
+ return NXT_ERROR;
+ }
+
+ m = map->map[0];
+
+ if (nxt_slow_path((nxt_gid_t) m.host != nxt_egid)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry for "
+ "host gid %d but unprivileged unit can only map itself "
+ "(gid %d) into child namespaces.", m.host, nxt_egid);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path(m.size > 1)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has an entry with "
+ "\"size\": %d, but for unprivileged unit it must be 1.",
+ m.size);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path((nxt_gid_t) m.container != creds->base_gid)) {
+ nxt_log(task, NXT_LOG_ERR,
+ "\"gidmap\" field has no \"container\" entry for gid %d.",
+ creds->base_gid);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ if (map->size == 0) {
+ if (creds->ngroups > 0
+ && !(creds->ngroups == 1 && creds->gids[0] == creds->base_gid))
+ {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no entries "
+ "but user \"%s\" has %d suplementary group%s.",
+ creds->user, creds->ngroups,
+ creds->ngroups > 1 ? "s" : "");
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ base_ok = 0;
+ gids_ok = 0;
+
+ for (i = 0; i < creds->ngroups; i++) {
+ gid_ok = 0;
+
+ for (j = 0; j < map->size; j++) {
+ m = map->map[j];
+
+ if (!base_ok && creds->base_gid >= (nxt_gid_t) m.container
+ && creds->base_gid < (nxt_gid_t) (m.container+m.size))
+ {
+ base_ok = 1;
+ }
+
+ if (creds->gids[i] >= (nxt_gid_t) m.container
+ && creds->gids[i] < (nxt_gid_t) (m.container+m.size))
+ {
+ gid_ok = 1;
+ break;
+ }
+ }
+
+ if (nxt_fast_path(gid_ok)) {
+ gids_ok++;
+ }
+ }
+
+ if (!base_ok) {
+ for (i = 0; i < map->size; i++) {
+ m = map->map[i];
+
+ if (creds->base_gid >= (nxt_gid_t) m.container
+ && creds->base_gid < (nxt_gid_t) (m.container+m.size))
+ {
+ base_ok = 1;
+ break;
+ }
+ }
+ }
+
+ if (nxt_slow_path(!base_ok)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has no \"container\" "
+ "entry for gid %d.", creds->base_gid);
+
+ return NXT_ERROR;
+ }
+
+ if (nxt_slow_path(gids_ok < creds->ngroups)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" field has missing "
+ "suplementary gid mappings (found %d out of %d).", gids_ok,
+ creds->ngroups);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
#endif
diff --git a/src/nxt_clone.h b/src/nxt_clone.h
index 50dec0b4..dcccf1db 100644
--- a/src/nxt_clone.h
+++ b/src/nxt_clone.h
@@ -7,11 +7,47 @@
#define _NXT_CLONE_INCLUDED_
+#if (NXT_HAVE_CLONE_NEWUSER)
+
+typedef struct {
+ nxt_int_t container;
+ nxt_int_t host;
+ nxt_int_t size;
+} nxt_clone_map_entry_t;
+
+typedef struct {
+ nxt_uint_t size;
+ nxt_clone_map_entry_t *map;
+} nxt_clone_credential_map_t;
+
+#endif
+
+typedef struct {
+ nxt_int_t flags;
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+ nxt_clone_credential_map_t uidmap;
+ nxt_clone_credential_map_t gidmap;
+#endif
+
+} nxt_clone_t;
+
+
pid_t nxt_clone(nxt_int_t flags);
+
#if (NXT_HAVE_CLONE_NEWUSER)
-nxt_int_t nxt_clone_proc_map(nxt_task_t *task, pid_t pid,
- nxt_process_clone_t *clone);
+
+#define NXT_CLONE_USER(flags) \
+ ((flags & CLONE_NEWUSER) == CLONE_NEWUSER)
+
+NXT_EXPORT nxt_int_t nxt_clone_credential_map(nxt_task_t *task, pid_t pid,
+ nxt_credential_t *creds, nxt_clone_t *clone);
+NXT_EXPORT nxt_int_t nxt_clone_vldt_credential_uidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds);
+NXT_EXPORT nxt_int_t nxt_clone_vldt_credential_gidmap(nxt_task_t *task,
+ nxt_clone_credential_map_t *map, nxt_credential_t *creds);
+
#endif
#endif /* _NXT_CLONE_INCLUDED_ */
diff --git a/src/nxt_credential.c b/src/nxt_credential.c
index 9f275b7d..168db9cf 100644
--- a/src/nxt_credential.c
+++ b/src/nxt_credential.c
@@ -280,61 +280,69 @@ free:
nxt_int_t
-nxt_credential_set(nxt_task_t *task, nxt_credential_t *uc)
+nxt_credential_setuid(nxt_task_t *task, nxt_credential_t *uc)
{
- nxt_debug(task, "user cred set: \"%s\" uid:%d base gid:%d",
- uc->user, uc->uid, uc->base_gid);
+ nxt_debug(task, "user cred set: \"%s\" uid:%d", uc->user, uc->uid);
- if (setgid(uc->base_gid) != 0) {
+ if (setuid(uc->uid) != 0) {
#if (NXT_HAVE_CLONE)
if (nxt_errno == EINVAL) {
- nxt_log(task, NXT_LOG_ERR, "The gid %d isn't valid in the "
- "application namespace.", uc->base_gid);
+ nxt_log(task, NXT_LOG_ERR, "The uid %d (user \"%s\") isn't "
+ "valid in the application namespace.", uc->uid, uc->user);
return NXT_ERROR;
}
#endif
- nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno);
+ nxt_alert(task, "setuid(%d) failed %E", uc->uid, nxt_errno);
return NXT_ERROR;
}
- if (uc->gids != NULL) {
- if (setgroups(uc->ngroups, uc->gids) != 0) {
+ return NXT_OK;
+}
-#if (NXT_HAVE_CLONE)
- if (nxt_errno == EINVAL) {
- nxt_log(task, NXT_LOG_ERR, "The user \"%s\" (uid: %d) has "
- "supplementary group ids not valid in the application "
- "namespace.", uc->user, uc->uid);
- return NXT_ERROR;
- }
-#endif
- nxt_alert(task, "setgroups(%i) failed %E", uc->ngroups, nxt_errno);
- return NXT_ERROR;
- }
+nxt_int_t
+nxt_credential_setgids(nxt_task_t *task, nxt_credential_t *uc)
+{
+ nxt_runtime_t *rt;
- } else {
- /* MacOSX fallback. */
- if (initgroups(uc->user, uc->base_gid) != 0) {
- nxt_alert(task, "initgroups(%s, %d) failed %E",
- uc->user, uc->base_gid, nxt_errno);
+ nxt_debug(task, "user cred set gids: base gid:%d, ngroups: %d",
+ uc->base_gid, uc->ngroups);
+
+ rt = task->thread->runtime;
+
+ if (setgid(uc->base_gid) != 0) {
+
+#if (NXT_HAVE_CLONE)
+ if (nxt_errno == EINVAL) {
+ nxt_log(task, NXT_LOG_ERR, "The gid %d isn't valid in the "
+ "application namespace.", uc->base_gid);
return NXT_ERROR;
}
+#endif
+
+ nxt_alert(task, "setgid(%d) failed %E", uc->base_gid, nxt_errno);
+ return NXT_ERROR;
}
- if (setuid(uc->uid) != 0) {
+ if (!rt->capabilities.setid) {
+ return NXT_OK;
+ }
+
+ if (nxt_slow_path(uc->ngroups > 0
+ && setgroups(uc->ngroups, uc->gids) != 0)) {
#if (NXT_HAVE_CLONE)
if (nxt_errno == EINVAL) {
- nxt_log(task, NXT_LOG_ERR, "The uid %d (user \"%s\") isn't "
- "valid in the application namespace.", uc->uid, uc->user);
+ nxt_log(task, NXT_LOG_ERR, "The user \"%s\" (uid: %d) has "
+ "supplementary group ids not valid in the application "
+ "namespace.", uc->user, uc->uid);
return NXT_ERROR;
}
#endif
- nxt_alert(task, "setuid(%d) failed %E", uc->uid, nxt_errno);
+ nxt_alert(task, "setgroups(%i) failed %E", uc->ngroups, nxt_errno);
return NXT_ERROR;
}
diff --git a/src/nxt_credential.h b/src/nxt_credential.h
index e9f59327..243eba83 100644
--- a/src/nxt_credential.h
+++ b/src/nxt_credential.h
@@ -21,7 +21,9 @@ typedef struct {
NXT_EXPORT nxt_int_t nxt_credential_get(nxt_task_t *task, nxt_mp_t *mp,
nxt_credential_t *uc, const char *group);
-NXT_EXPORT nxt_int_t nxt_credential_set(nxt_task_t *task,
+NXT_EXPORT nxt_int_t nxt_credential_setuid(nxt_task_t *task,
+ nxt_credential_t *uc);
+NXT_EXPORT nxt_int_t nxt_credential_setgids(nxt_task_t *task,
nxt_credential_t *uc);
diff --git a/src/nxt_lib.c b/src/nxt_lib.c
index db3d29c1..1634a2b8 100644
--- a/src/nxt_lib.c
+++ b/src/nxt_lib.c
@@ -43,6 +43,8 @@ nxt_lib_start(const char *app, char **argv, char ***envp)
nxt_pid = getpid();
nxt_ppid = getppid();
+ nxt_euid = geteuid();
+ nxt_egid = getegid();
#if (NXT_DEBUG)
diff --git a/src/nxt_main_process.c b/src/nxt_main_process.c
index 02b0c293..79b9ee1f 100644
--- a/src/nxt_main_process.c
+++ b/src/nxt_main_process.c
@@ -80,10 +80,22 @@ static nxt_int_t nxt_process_init_name_set(nxt_process_init_t *init,
static nxt_int_t nxt_process_init_creds_set(nxt_task_t *task,
nxt_process_init_t *init, nxt_str_t *user, nxt_str_t *group);
-static nxt_int_t nxt_init_set_isolation(nxt_task_t *task,
- nxt_process_init_t *init, nxt_conf_value_t *isolation);
-static nxt_int_t nxt_init_set_ns(nxt_task_t *task, nxt_process_init_t *init,
- nxt_conf_value_t *ns);
+static nxt_int_t nxt_init_isolation(nxt_task_t *task,
+ nxt_conf_value_t *isolation, nxt_process_init_t *init);
+#if (NXT_HAVE_CLONE)
+static nxt_int_t nxt_init_clone_flags(nxt_task_t *task,
+ nxt_conf_value_t *namespaces, nxt_process_init_t *init);
+#endif
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+static nxt_int_t nxt_init_isolation_creds(nxt_task_t *task,
+ nxt_conf_value_t *isolation, nxt_process_init_t *init);
+static nxt_int_t nxt_init_vldt_isolation_creds(nxt_task_t *task,
+ nxt_process_init_t *init);
+static nxt_int_t nxt_init_isolation_credential_map(nxt_task_t *task,
+ nxt_mp_t *mem_pool, nxt_conf_value_t *map_array,
+ nxt_clone_credential_map_t *map);
+#endif
const nxt_sig_event_t nxt_main_process_signals[] = {
nxt_event_signal(SIGHUP, nxt_main_process_signal_handler),
@@ -641,6 +653,7 @@ static nxt_int_t
nxt_main_start_worker_process(nxt_task_t *task, nxt_runtime_t *rt,
nxt_common_app_conf_t *app_conf, uint32_t stream)
{
+ nxt_int_t cap_setid;
nxt_int_t ret;
nxt_process_init_t *init;
@@ -649,7 +662,22 @@ nxt_main_start_worker_process(nxt_task_t *task, nxt_runtime_t *rt,
return NXT_ERROR;
}
- if (rt->capabilities.setid) {
+ cap_setid = rt->capabilities.setid;
+
+ if (app_conf->isolation != NULL) {
+ ret = nxt_init_isolation(task, app_conf->isolation, init);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+ }
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+ if (NXT_CLONE_USER(init->isolation.clone.flags)) {
+ cap_setid = 1;
+ }
+#endif
+
+ if (cap_setid) {
ret = nxt_process_init_creds_set(task, init, &app_conf->user,
&app_conf->group);
if (nxt_slow_path(ret != NXT_OK)) {
@@ -679,10 +707,12 @@ nxt_main_start_worker_process(nxt_task_t *task, nxt_runtime_t *rt,
init->data = app_conf;
init->stream = stream;
- ret = nxt_init_set_isolation(task, init, app_conf->isolation);
+#if (NXT_HAVE_CLONE_NEWUSER)
+ ret = nxt_init_vldt_isolation_creds(task, init);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
+#endif
return nxt_main_create_worker_process(task, rt, init);
@@ -1487,45 +1517,178 @@ nxt_main_port_access_log_handler(nxt_task_t *task, nxt_port_recv_msg_t *msg)
static nxt_int_t
-nxt_init_set_isolation(nxt_task_t *task, nxt_process_init_t *init,
- nxt_conf_value_t *isolation)
+nxt_init_isolation(nxt_task_t *task, nxt_conf_value_t *isolation,
+ nxt_process_init_t *init)
+{
+#if (NXT_HAVE_CLONE)
+ nxt_int_t ret;
+ nxt_conf_value_t *obj;
+
+ static nxt_str_t nsname = nxt_string("namespaces");
+
+ obj = nxt_conf_get_object_member(isolation, &nsname, NULL);
+ if (obj != NULL) {
+ ret = nxt_init_clone_flags(task, obj, init);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NXT_ERROR;
+ }
+ }
+#endif
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+ ret = nxt_init_isolation_creds(task, isolation, init);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NXT_ERROR;
+ }
+#endif
+
+ return NXT_OK;
+}
+
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+
+static nxt_int_t
+nxt_init_isolation_creds(nxt_task_t *task, nxt_conf_value_t *isolation,
+ nxt_process_init_t *init)
{
nxt_int_t ret;
- nxt_conf_value_t *object;
+ nxt_clone_t *clone;
+ nxt_conf_value_t *array;
- static nxt_str_t nsname = nxt_string("namespaces");
static nxt_str_t uidname = nxt_string("uidmap");
static nxt_str_t gidname = nxt_string("gidmap");
- if (isolation == NULL) {
+ clone = &init->isolation.clone;
+
+ array = nxt_conf_get_object_member(isolation, &uidname, NULL);
+ if (array != NULL) {
+ ret = nxt_init_isolation_credential_map(task, init->mem_pool, array,
+ &clone->uidmap);
+
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NXT_ERROR;
+ }
+ }
+
+ array = nxt_conf_get_object_member(isolation, &gidname, NULL);
+ if (array != NULL) {
+ ret = nxt_init_isolation_credential_map(task, init->mem_pool, array,
+ &clone->gidmap);
+
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NXT_ERROR;
+ }
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_init_vldt_isolation_creds(nxt_task_t *task, nxt_process_init_t *init)
+{
+ nxt_int_t ret;
+ nxt_clone_t *clone;
+
+ clone = &init->isolation.clone;
+
+ if (clone->uidmap.size == 0 && clone->gidmap.size == 0) {
return NXT_OK;
}
- object = nxt_conf_get_object_member(isolation, &nsname, NULL);
- if (object != NULL) {
- ret = nxt_init_set_ns(task, init, object);
- if (ret != NXT_OK) {
- return ret;
+ if (!NXT_CLONE_USER(clone->flags)) {
+ if (nxt_slow_path(clone->uidmap.size > 0)) {
+ nxt_log(task, NXT_LOG_ERR, "\"uidmap\" is set but "
+ "\"isolation.namespaces.credential\" is false or unset");
+
+ return NXT_ERROR;
}
+
+ if (nxt_slow_path(clone->gidmap.size > 0)) {
+ nxt_log(task, NXT_LOG_ERR, "\"gidmap\" is set but "
+ "\"isolation.namespaces.credential\" is false or unset");
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+ }
+
+ ret = nxt_clone_vldt_credential_uidmap(task, &clone->uidmap,
+ init->user_cred);
+
+ if (nxt_slow_path(ret != NXT_OK)) {
+ return NXT_ERROR;
+ }
+
+ return nxt_clone_vldt_credential_gidmap(task, &clone->gidmap,
+ init->user_cred);
+}
+
+
+static nxt_int_t
+nxt_init_isolation_credential_map(nxt_task_t *task, nxt_mp_t *mem_pool,
+ nxt_conf_value_t *map_array, nxt_clone_credential_map_t *map)
+{
+ nxt_int_t ret;
+ nxt_uint_t i;
+ nxt_conf_value_t *obj;
+
+ static nxt_conf_map_t nxt_clone_map_entry_conf[] = {
+ {
+ nxt_string("container"),
+ NXT_CONF_MAP_INT,
+ offsetof(nxt_clone_map_entry_t, container),
+ },
+
+ {
+ nxt_string("host"),
+ NXT_CONF_MAP_INT,
+ offsetof(nxt_clone_map_entry_t, host),
+ },
+
+ {
+ nxt_string("size"),
+ NXT_CONF_MAP_INT,
+ offsetof(nxt_clone_map_entry_t, size),
+ },
+ };
+
+ map->size = nxt_conf_array_elements_count(map_array);
+
+ if (map->size == 0) {
+ return NXT_OK;
}
- object = nxt_conf_get_object_member(isolation, &uidname, NULL);
- if (object != NULL) {
- init->isolation.clone.uidmap = object;
+ map->map = nxt_mp_alloc(mem_pool,
+ map->size * sizeof(nxt_clone_map_entry_t));
+ if (nxt_slow_path(map->map == NULL)) {
+ return NXT_ERROR;
}
- object = nxt_conf_get_object_member(isolation, &gidname, NULL);
- if (object != NULL) {
- init->isolation.clone.gidmap = object;
+ for (i = 0; i < map->size; i++) {
+ obj = nxt_conf_get_array_element(map_array, i);
+
+ ret = nxt_conf_map_object(mem_pool, obj, nxt_clone_map_entry_conf,
+ nxt_nitems(nxt_clone_map_entry_conf),
+ map->map + i);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ nxt_alert(task, "clone map entry map error");
+ return NXT_ERROR;
+ }
}
return NXT_OK;
}
+#endif
+
+#if (NXT_HAVE_CLONE)
static nxt_int_t
-nxt_init_set_ns(nxt_task_t *task, nxt_process_init_t *init,
- nxt_conf_value_t *namespaces)
+nxt_init_clone_flags(nxt_task_t *task, nxt_conf_value_t *namespaces,
+ nxt_process_init_t *init)
{
uint32_t index;
nxt_str_t name;
@@ -1592,6 +1755,8 @@ nxt_init_set_ns(nxt_task_t *task, nxt_process_init_t *init,
return NXT_OK;
}
+#endif
+
static nxt_process_init_t *
nxt_process_init_create(nxt_task_t *task, nxt_process_type_t type,
diff --git a/src/nxt_process.c b/src/nxt_process.c
index 75c6ef25..035f747f 100644
--- a/src/nxt_process.c
+++ b/src/nxt_process.c
@@ -23,6 +23,12 @@ nxt_pid_t nxt_pid;
/* An original parent process pid. */
nxt_pid_t nxt_ppid;
+/* A cached process effective uid */
+nxt_uid_t nxt_euid;
+
+/* A cached process effective gid */
+nxt_gid_t nxt_egid;
+
nxt_bool_t nxt_proc_conn_matrix[NXT_PROCESS_MAX][NXT_PROCESS_MAX] = {
{ 1, 1, 1, 1, 1 },
{ 1, 0, 0, 0, 0 },
@@ -207,8 +213,9 @@ nxt_process_create(nxt_task_t *task, nxt_process_t *process)
}
#if (NXT_HAVE_CLONE && NXT_HAVE_CLONE_NEWUSER)
- if ((init->isolation.clone.flags & CLONE_NEWUSER) == CLONE_NEWUSER) {
- ret = nxt_clone_proc_map(task, pid, &init->isolation.clone);
+ if (NXT_CLONE_USER(init->isolation.clone.flags)) {
+ ret = nxt_clone_credential_map(task, pid, init->user_cred,
+ &init->isolation.clone);
if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
@@ -257,7 +264,7 @@ cleanup:
static void
nxt_process_start(nxt_task_t *task, nxt_process_t *process)
{
- nxt_int_t ret;
+ nxt_int_t ret, cap_setid;
nxt_port_t *port, *main_port;
nxt_thread_t *thread;
nxt_runtime_t *rt;
@@ -276,9 +283,22 @@ nxt_process_start(nxt_task_t *task, nxt_process_t *process)
nxt_random_init(&thread->random);
- if (rt->capabilities.setid && init->user_cred != NULL) {
- ret = nxt_credential_set(task, init->user_cred);
- if (ret != NXT_OK) {
+ cap_setid = rt->capabilities.setid;
+
+#if (NXT_HAVE_CLONE_NEWUSER)
+ if (!cap_setid && NXT_CLONE_USER(init->isolation.clone.flags)) {
+ cap_setid = 1;
+ }
+#endif
+
+ if (cap_setid) {
+ ret = nxt_credential_setgids(task, init->user_cred);
+ if (nxt_slow_path(ret != NXT_OK)) {
+ goto fail;
+ }
+
+ ret = nxt_credential_setuid(task, init->user_cred);
+ if (nxt_slow_path(ret != NXT_OK)) {
goto fail;
}
}
diff --git a/src/nxt_process.h b/src/nxt_process.h
index 457d2299..343fffb8 100644
--- a/src/nxt_process.h
+++ b/src/nxt_process.h
@@ -7,18 +7,14 @@
#ifndef _NXT_PROCESS_H_INCLUDED_
#define _NXT_PROCESS_H_INCLUDED_
-#include <nxt_conf.h>
+#if (NXT_HAVE_CLONE)
+#include <nxt_clone.h>
+#endif
typedef pid_t nxt_pid_t;
-typedef struct {
- nxt_int_t flags;
- nxt_conf_value_t *uidmap;
- nxt_conf_value_t *gidmap;
-} nxt_process_clone_t;
-
typedef struct nxt_process_init_s nxt_process_init_t;
typedef nxt_int_t (*nxt_process_start_t)(nxt_task_t *task, void *data);
typedef nxt_int_t (*nxt_process_restart_t)(nxt_task_t *task, nxt_runtime_t *rt,
@@ -39,7 +35,9 @@ struct nxt_process_init_s {
uint32_t stream;
union {
- nxt_process_clone_t clone;
+#if (NXT_HAVE_CLONE)
+ nxt_clone_t clone;
+#endif
} isolation;
};
@@ -118,6 +116,8 @@ nxt_port_t *nxt_process_connected_port_find(nxt_process_t *process,
void nxt_worker_process_quit_handler(nxt_task_t *task,
nxt_port_recv_msg_t *msg);
+void nxt_init_destroy(nxt_runtime_t *rt, nxt_process_init_t *init);
+
#if (NXT_HAVE_SETPROCTITLE)
@@ -146,6 +146,8 @@ NXT_EXPORT void nxt_process_title(nxt_task_t *task, const char *fmt, ...);
NXT_EXPORT extern nxt_pid_t nxt_pid;
NXT_EXPORT extern nxt_pid_t nxt_ppid;
+NXT_EXPORT extern nxt_uid_t nxt_euid;
+NXT_EXPORT extern nxt_gid_t nxt_egid;
NXT_EXPORT extern char **nxt_process_argv;
NXT_EXPORT extern char ***nxt_process_environ;
diff --git a/src/test/nxt_clone_test.c b/src/test/nxt_clone_test.c
new file mode 100644
index 00000000..15d36557
--- /dev/null
+++ b/src/test/nxt_clone_test.c
@@ -0,0 +1,601 @@
+/*
+ * Copyright (C) NGINX, Inc.
+ * Copyright (C) Valentin V. Bartenev
+ */
+
+#include <nxt_main.h>
+#include <nxt_conf.h>
+#include "nxt_tests.h"
+
+
+#define UIDMAP 1
+#define GIDMAP 2
+
+
+typedef struct {
+ nxt_int_t map_type;
+ nxt_str_t map_data;
+ nxt_int_t setid;
+ nxt_credential_t creds;
+ nxt_uid_t unit_euid;
+ nxt_gid_t unit_egid;
+ nxt_int_t result;
+ nxt_str_t errmsg;
+} nxt_clone_creds_testcase_t;
+
+typedef struct {
+ nxt_clone_creds_testcase_t *tc;
+} nxt_clone_creds_ctx_t;
+
+
+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);
+void nxt_cdecl nxt_clone_test_log_handler(nxt_uint_t level, nxt_log_t *log,
+ const char *fmt, ...);
+nxt_int_t nxt_clone_test_map_assert(nxt_task_t *task,
+ nxt_clone_creds_testcase_t *tc, nxt_clone_credential_map_t *map);
+static nxt_int_t nxt_clone_test_parse_map(nxt_task_t *task,
+ nxt_str_t *map_str, nxt_clone_credential_map_t *map);
+
+
+nxt_log_t *test_log;
+
+static nxt_gid_t gids[] = {1000, 10000, 60000};
+
+static nxt_clone_creds_testcase_t testcases[] = {
+ {
+ /*
+ * Unprivileged unit
+ *
+ * if no uid mapping and app creds and unit creds are the same,
+ * then we automatically add a map for the creds->uid.
+ * Then, child process can safely setuid(creds->uid) in
+ * the new namespace.
+ */
+ UIDMAP,
+ nxt_string(""),
+ 0,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string(""),
+ 0,
+ {"johndoe", 10000, 10000, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"root", 0, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"nobody", 65534, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1}]"),
+ 0,
+ {"root", 0, 0, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"uidmap\" field has 2 entries but unprivileged unit has "
+ "a maximum of 1 map.")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1}]"),
+ 1, /* privileged */
+ {"root", 0, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
+ 1, /* privileged */
+ {"johndoe", 500, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
+ 1, /* privileged */
+ {"johndoe", 1000, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
+ 1, /* privileged */
+ {"johndoe", 1500, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
+ 1, /* privileged */
+ {"johndoe", 1999, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ UIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1000},"
+ " {\"container\": 1000, \"host\": 2000, \"size\": 1000}]"),
+ 1, /* privileged */
+ {"johndoe", 2000, 0, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"uidmap\" field has no \"container\" entry for user "
+ "\"johndoe\" (uid 2000)")
+ },
+ {
+ /*
+ * Unprivileged unit
+ *
+ * if no gid mapping and app creds and unit creds are the same,
+ * then we automatically add a map for the creds->base_gid.
+ * Then, child process can safely setgid(creds->base_gid) in
+ * the new namespace.
+ */
+ GIDMAP,
+ nxt_string("[]"),
+ 0,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /*
+ * Unprivileged unit
+ *
+ * Inside the new namespace, we can have any gid but it
+ * should map to parent gid (in this case 1000) in parent
+ * namespace.
+ */
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"root", 0, 0, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /*
+ * Unprivileged unit
+ *
+ * There's no mapping for "johndoe" (gid 1000) inside the namespace.
+ */
+ GIDMAP,
+ nxt_string("[{\"container\": 65535, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has no \"container\" entry for "
+ "gid 1000.")
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 2}]"),
+ 0,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has an entry with \"size\": 2, but "
+ "for unprivileged unit it must be 1.")
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 1001, \"size\": 1}]"),
+ 0,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has an entry for host gid 1001 but "
+ "unprivileged unit can only map itself (gid 1000) "
+ "into child namespaces.")
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 1000, \"size\": 1}]"),
+ 0,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("unprivileged unit disallow supplementary groups for "
+ "new namespace (user \"johndoe\" has 3 groups).")
+ },
+
+ /* privileged unit */
+
+ /* not root with capabilities */
+ {
+ GIDMAP,
+ nxt_string("[]"),
+ 1,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ GIDMAP,
+ nxt_string(""),
+ 1,
+ {"johndoe", 1000, 1000, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /* missing gid of {"user": "nobody"} */
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1}]"),
+ 1,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has no \"container\" entry for "
+ "gid 65534.")
+ },
+ {
+ /* solves the previous by mapping 65534 gids */
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 65535}]"),
+ 1,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /* solves by adding a separate mapping */
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 1000, \"size\": 1},"
+ " {\"container\": 65534, \"host\": 1000, \"size\": 1}]"),
+ 1,
+ {"nobody", 65534, 65534, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /*
+ * Map a big range
+ */
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 0, \"size\": 200000}]"),
+ 1,
+ {"johndoe", 100000, 100000, 0, NULL},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string("")
+ },
+ {
+ /*
+ * Validate if supplementary groups are mapped
+ */
+ GIDMAP,
+ nxt_string("[]"),
+ 1,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has no entries but user \"johndoe\" "
+ "has 3 suplementary groups."),
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 0, \"host\": 0, \"size\": 1}]"),
+ 1,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has no \"container\" entry for "
+ "gid 1000."),
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1}]"),
+ 1,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has missing suplementary gid mappings "
+ "(found 1 out of 3)."),
+ },
+ {
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1},"
+ " {\"container\": 10000, \"host\": 10000, \"size\": 1}]"),
+ 1,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_ERROR,
+ nxt_string("\"gidmap\" field has missing suplementary gid mappings "
+ "(found 2 out of 3)."),
+ },
+ {
+ /*
+ * Fix all mappings
+ */
+ GIDMAP,
+ nxt_string("[{\"container\": 1000, \"host\": 0, \"size\": 1},"
+ "{\"container\": 10000, \"host\": 10000, \"size\": 1},"
+ " {\"container\": 60000, \"host\": 60000, \"size\": 1}]"),
+ 1,
+ {"johndoe", 1000, 1000, 3, gids},
+ 1000, 1000,
+ NXT_OK,
+ nxt_string(""),
+ },
+};
+
+
+void nxt_cdecl
+nxt_clone_test_log_handler(nxt_uint_t level, nxt_log_t *log,
+ const char *fmt, ...)
+{
+ u_char *p, *end;
+ va_list args;
+ nxt_clone_creds_ctx_t *ctx;
+ nxt_clone_creds_testcase_t *tc;
+ u_char msg[NXT_MAX_ERROR_STR];
+
+ p = msg;
+ end = msg + NXT_MAX_ERROR_STR;
+
+ ctx = log->ctx;
+ tc = ctx->tc;
+
+ va_start(args, fmt);
+ p = nxt_vsprintf(p, end, fmt, args);
+ va_end(args);
+
+ *p++ = '\0';
+
+ if (tc->result == NXT_OK && level == NXT_LOG_DEBUG) {
+ return;
+ }
+
+ if (tc->errmsg.length == 0) {
+ nxt_log_error(NXT_LOG_ERR, &nxt_main_log, "unexpected log: %s", msg);
+ return;
+ }
+
+ if (!nxt_str_eq(&tc->errmsg, msg, (nxt_uint_t) (p - msg - 1))) {
+ nxt_log_error(NXT_LOG_ERR, &nxt_main_log,
+ "error log mismatch: got [%s] but wants [%V]",
+ msg, &tc->errmsg);
+ return;
+ }
+}
+
+
+nxt_int_t
+nxt_clone_creds_test(nxt_thread_t *thr)
+{
+ nxt_mp_t *mp;
+ nxt_int_t ret;
+ nxt_uint_t count, i;
+ nxt_task_t *task;
+ nxt_runtime_t rt;
+ nxt_clone_creds_ctx_t ctx;
+
+ nxt_log_t nxt_clone_creds_log = {
+ NXT_LOG_INFO,
+ 0,
+ nxt_clone_test_log_handler,
+ NULL,
+ &ctx
+ };
+
+ nxt_thread_time_update(thr);
+
+ thr->runtime = &rt;
+
+ task = thr->task;
+
+ mp = nxt_mp_create(1024, 128, 256, 32);
+ if (mp == NULL) {
+ return NXT_ERROR;
+ }
+
+ rt.mem_pool = mp;
+
+ test_log = task->log;
+ task->log = &nxt_clone_creds_log;
+ task->thread = thr;
+
+ count = sizeof(testcases)/sizeof(nxt_clone_creds_testcase_t);
+
+ for (i = 0; i < count; i++) {
+ ret = nxt_clone_test_mappings(task, mp, &ctx, &testcases[i]);
+
+ if (ret != NXT_OK) {
+ goto fail;
+ }
+ }
+
+ ret = NXT_OK;
+
+ nxt_log_error(NXT_LOG_NOTICE, test_log, "clone creds test passed");
+
+fail:
+ task->log = test_log;
+ nxt_mp_destroy(mp);
+
+ return ret;
+}
+
+
+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)
+{
+ nxt_int_t ret;
+ nxt_runtime_t *rt;
+ nxt_clone_credential_map_t map;
+
+ rt = task->thread->runtime;
+
+ map.size = 0;
+
+ if (tc->map_data.length > 0) {
+ ret = nxt_clone_test_parse_map(task, &tc->map_data, &map);
+ if (ret != NXT_OK) {
+ return NXT_ERROR;
+ }
+ }
+
+ rt->capabilities.setid = tc->setid;
+
+ nxt_euid = tc->unit_euid;
+ nxt_egid = tc->unit_egid;
+
+ ctx->tc = tc;
+
+ if (nxt_clone_test_map_assert(task, tc, &map) != NXT_OK) {
+ return NXT_ERROR;
+ }
+
+ if (tc->setid && nxt_euid != 0) {
+ /*
+ * Running as root should have the same behavior as
+ * passing Linux capabilities.
+ */
+
+ nxt_euid = 0;
+ nxt_egid = 0;
+
+ if (nxt_clone_test_map_assert(task, tc, &map) != NXT_OK) {
+ return NXT_ERROR;
+ }
+ }
+
+ return NXT_OK;
+}
+
+
+nxt_int_t
+nxt_clone_test_map_assert(nxt_task_t *task, nxt_clone_creds_testcase_t *tc,
+ nxt_clone_credential_map_t *map)
+{
+ nxt_int_t ret;
+
+ if (tc->map_type == UIDMAP) {
+ ret = nxt_clone_vldt_credential_uidmap(task, map, &tc->creds);
+ } else {
+ ret = nxt_clone_vldt_credential_gidmap(task, map, &tc->creds);
+ }
+
+ if (ret != tc->result) {
+ nxt_log_error(NXT_LOG_ERR, &nxt_main_log,
+ "return %d instead of %d (map: %V)", ret, tc->result,
+ &tc->map_data);
+
+ return NXT_ERROR;
+ }
+
+ return NXT_OK;
+}
+
+
+static nxt_int_t
+nxt_clone_test_parse_map(nxt_task_t *task, nxt_str_t *map_str,
+ nxt_clone_credential_map_t *map)
+{
+ nxt_uint_t i;
+ nxt_runtime_t *rt;
+ nxt_conf_value_t *array, *obj, *value;
+
+ static nxt_str_t host_name = nxt_string("host");
+ static nxt_str_t cont_name = nxt_string("container");
+ static nxt_str_t size_name = nxt_string("size");
+
+ rt = task->thread->runtime;
+
+ array = nxt_conf_json_parse_str(rt->mem_pool, map_str);
+ if (array == NULL) {
+ return NXT_ERROR;
+ }
+
+ map->size = nxt_conf_array_elements_count(array);
+
+ if (map->size == 0) {
+ return NXT_OK;
+ }
+
+ map->map = nxt_mp_alloc(rt->mem_pool,
+ map->size * sizeof(nxt_clone_map_entry_t));
+
+ if (map->map == NULL) {
+ return NXT_ERROR;
+ }
+
+ for (i = 0; i < map->size; i++) {
+ obj = nxt_conf_get_array_element(array, i);
+
+ value = nxt_conf_get_object_member(obj, &host_name, NULL);
+ map->map[i].host = nxt_conf_get_integer(value);
+
+ value = nxt_conf_get_object_member(obj, &cont_name, NULL);
+ map->map[i].container = nxt_conf_get_integer(value);
+
+ value = nxt_conf_get_object_member(obj, &size_name, NULL);
+ map->map[i].size = nxt_conf_get_integer(value);
+ }
+
+ return NXT_OK;
+}
diff --git a/src/test/nxt_tests.c b/src/test/nxt_tests.c
index 7cba0f69..901d76c3 100644
--- a/src/test/nxt_tests.c
+++ b/src/test/nxt_tests.c
@@ -162,5 +162,11 @@ main(int argc, char **argv)
return 1;
}
+#if (NXT_HAVE_CLONE_NEWUSER)
+ if (nxt_clone_creds_test(thr) != NXT_OK) {
+ return 1;
+ }
+#endif
+
return 0;
}
diff --git a/src/test/nxt_tests.h b/src/test/nxt_tests.h
index be4168cf..d531cc7d 100644
--- a/src/test/nxt_tests.h
+++ b/src/test/nxt_tests.h
@@ -64,6 +64,7 @@ nxt_int_t nxt_malloc_test(nxt_thread_t *thr);
nxt_int_t nxt_utf8_test(nxt_thread_t *thr);
nxt_int_t nxt_http_parse_test(nxt_thread_t *thr);
nxt_int_t nxt_strverscmp_test(nxt_thread_t *thr);
+nxt_int_t nxt_clone_creds_test(nxt_thread_t *thr);
#endif /* _NXT_TESTS_H_INCLUDED_ */