YARN-7626. Allow regular expression matching in container-executor.cfg for devices and named docker volumes mount. (Zian Chen via wangda)
Change-Id: If461277d4557922ab7e4dce9dd8dc5d0d5f22710 (cherry picked from commit 88f9138e12d2d5a1bd13f0915acef93037c1d086)
This commit is contained in:
parent
4d53ef7eef
commit
037d783483
@ -115,6 +115,22 @@ int check_trusted_image(const struct configuration *command_config, const struct
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int is_regex(const char *str) {
|
||||||
|
// regex should begin with prefix "regex:"
|
||||||
|
return (strncmp(str, "regex:", 6) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_volume_name(const char *volume_name) {
|
||||||
|
const char *regex_str = "^[a-zA-Z0-9]([a-zA-Z0-9_.-]*)$";
|
||||||
|
// execute_regex_match return 0 is matched success
|
||||||
|
return execute_regex_match(regex_str, volume_name) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int is_volume_name_matched_by_regex(const char* requested, const char* pattern) {
|
||||||
|
// execute_regex_match return 0 is matched success
|
||||||
|
return is_volume_name(requested) && (execute_regex_match(pattern + sizeof("regex:"), requested) == 0);
|
||||||
|
}
|
||||||
|
|
||||||
static int add_param_to_command_if_allowed(const struct configuration *command_config,
|
static int add_param_to_command_if_allowed(const struct configuration *command_config,
|
||||||
const struct configuration *executor_cfg,
|
const struct configuration *executor_cfg,
|
||||||
const char *key, const char *allowed_key, const char *param,
|
const char *key, const char *allowed_key, const char *param,
|
||||||
@ -150,6 +166,7 @@ static int add_param_to_command_if_allowed(const struct configuration *command_c
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (permitted_values != NULL) {
|
if (permitted_values != NULL) {
|
||||||
|
// Values are user requested.
|
||||||
for (i = 0; values[i] != NULL; ++i) {
|
for (i = 0; values[i] != NULL; ++i) {
|
||||||
memset(tmp_buffer, 0, tmp_buffer_size);
|
memset(tmp_buffer, 0, tmp_buffer_size);
|
||||||
permitted = 0;
|
permitted = 0;
|
||||||
@ -162,13 +179,30 @@ static int add_param_to_command_if_allowed(const struct configuration *command_c
|
|||||||
goto free_and_exit;
|
goto free_and_exit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Iterate through each permitted value
|
||||||
|
char* dst = NULL;
|
||||||
|
char* pattern = NULL;
|
||||||
|
|
||||||
for (j = 0; permitted_values[j] != NULL; ++j) {
|
for (j = 0; permitted_values[j] != NULL; ++j) {
|
||||||
if (prefix == 0) {
|
if (prefix == 0) {
|
||||||
ret = strcmp(values[i], permitted_values[j]);
|
ret = strcmp(values[i], permitted_values[j]);
|
||||||
|
} else {
|
||||||
|
// If permitted-Values[j] is a REGEX, use REGEX to compare
|
||||||
|
if (is_regex(permitted_values[j])) {
|
||||||
|
size_t offset = tmp_ptr - values[i];
|
||||||
|
dst = (char *) alloc_and_clear_memory(offset, sizeof(char));
|
||||||
|
strncpy(dst, values[i], offset);
|
||||||
|
dst[tmp_ptr - values[i]] = '\0';
|
||||||
|
pattern = (char *) alloc_and_clear_memory((size_t)(strlen(permitted_values[j]) - 6), sizeof(char));
|
||||||
|
strcpy(pattern, permitted_values[j] + 6);
|
||||||
|
ret = execute_regex_match(pattern, dst);
|
||||||
} else {
|
} else {
|
||||||
ret = strncmp(values[i], permitted_values[j], tmp_ptr - values[i]);
|
ret = strncmp(values[i], permitted_values[j], tmp_ptr - values[i]);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
free(dst);
|
||||||
|
free(pattern);
|
||||||
permitted = 1;
|
permitted = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@ -941,9 +975,10 @@ static int set_devices(const struct configuration *command_config, const struct
|
|||||||
* 2. If the path is a directory, add a '/' at the end (if not present)
|
* 2. If the path is a directory, add a '/' at the end (if not present)
|
||||||
* 3. Return a copy of the canonicalised path(to be freed by the caller)
|
* 3. Return a copy of the canonicalised path(to be freed by the caller)
|
||||||
* @param mount path to be canonicalised
|
* @param mount path to be canonicalised
|
||||||
|
* @param isRegexAllowed whether regex matching is allowed for normalize mount
|
||||||
* @return pointer to canonicalised path, NULL on error
|
* @return pointer to canonicalised path, NULL on error
|
||||||
*/
|
*/
|
||||||
static char* normalize_mount(const char* mount) {
|
static char* normalize_mount(const char* mount, int isRegexAllowed) {
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
struct stat buff;
|
struct stat buff;
|
||||||
char *ret_ptr = NULL, *real_mount = NULL;
|
char *ret_ptr = NULL, *real_mount = NULL;
|
||||||
@ -953,10 +988,16 @@ static char* normalize_mount(const char* mount) {
|
|||||||
real_mount = realpath(mount, NULL);
|
real_mount = realpath(mount, NULL);
|
||||||
if (real_mount == NULL) {
|
if (real_mount == NULL) {
|
||||||
// If mount is a valid named volume, just return it and let docker decide
|
// If mount is a valid named volume, just return it and let docker decide
|
||||||
if (validate_volume_name(mount) == 0) {
|
if (is_volume_name(mount)) {
|
||||||
return strdup(mount);
|
return strdup(mount);
|
||||||
}
|
}
|
||||||
|
// we only allow permitted mount to be REGEX, for permitted mount, we check
|
||||||
|
// if it's a valid REGEX return; for user mount, we need to strictly check
|
||||||
|
if (isRegexAllowed) {
|
||||||
|
if (is_regex(mount)) {
|
||||||
|
return strdup(mount);
|
||||||
|
}
|
||||||
|
}
|
||||||
fprintf(ERRORFILE, "Could not determine real path of mount '%s'\n", mount);
|
fprintf(ERRORFILE, "Could not determine real path of mount '%s'\n", mount);
|
||||||
free(real_mount);
|
free(real_mount);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -987,14 +1028,14 @@ static char* normalize_mount(const char* mount) {
|
|||||||
return ret_ptr;
|
return ret_ptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int normalize_mounts(char **mounts) {
|
static int normalize_mounts(char **mounts, int isRegexAllowed) {
|
||||||
int i = 0;
|
int i = 0;
|
||||||
char *tmp = NULL;
|
char *tmp = NULL;
|
||||||
if (mounts == NULL) {
|
if (mounts == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
for (i = 0; mounts[i] != NULL; ++i) {
|
for (i = 0; mounts[i] != NULL; ++i) {
|
||||||
tmp = normalize_mount(mounts[i]);
|
tmp = normalize_mount(mounts[i], isRegexAllowed);
|
||||||
if (tmp == NULL) {
|
if (tmp == NULL) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
@ -1007,7 +1048,7 @@ static int normalize_mounts(char **mounts) {
|
|||||||
static int check_mount_permitted(const char **permitted_mounts, const char *requested) {
|
static int check_mount_permitted(const char **permitted_mounts, const char *requested) {
|
||||||
int i = 0, ret = 0;
|
int i = 0, ret = 0;
|
||||||
size_t permitted_mount_len = 0;
|
size_t permitted_mount_len = 0;
|
||||||
char *normalized_path = normalize_mount(requested);
|
char *normalized_path = normalize_mount(requested, 0);
|
||||||
if (permitted_mounts == NULL) {
|
if (permitted_mounts == NULL) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1019,6 +1060,13 @@ static int check_mount_permitted(const char **permitted_mounts, const char *requ
|
|||||||
ret = 1;
|
ret = 1;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
// if (permitted_mounts[i] is a REGEX): use REGEX to compare; return
|
||||||
|
if (is_regex(permitted_mounts[i]) &&
|
||||||
|
is_volume_name_matched_by_regex(normalized_path, permitted_mounts[i])) {
|
||||||
|
ret = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
// directory check
|
// directory check
|
||||||
permitted_mount_len = strlen(permitted_mounts[i]);
|
permitted_mount_len = strlen(permitted_mounts[i]);
|
||||||
struct stat path_stat;
|
struct stat path_stat;
|
||||||
@ -1059,7 +1107,7 @@ static int add_mounts(const struct configuration *command_config, const struct c
|
|||||||
CONTAINER_EXECUTOR_CFG_DOCKER_SECTION, conf, ",");
|
CONTAINER_EXECUTOR_CFG_DOCKER_SECTION, conf, ",");
|
||||||
char **values = get_configuration_values_delimiter(key, DOCKER_COMMAND_FILE_SECTION, command_config, ",");
|
char **values = get_configuration_values_delimiter(key, DOCKER_COMMAND_FILE_SECTION, command_config, ",");
|
||||||
char *tmp_buffer_2 = NULL, *mount_src = NULL;
|
char *tmp_buffer_2 = NULL, *mount_src = NULL;
|
||||||
const char *container_executor_cfg_path = normalize_mount(get_config_path(""));
|
const char *container_executor_cfg_path = normalize_mount(get_config_path(""), 0);
|
||||||
int i = 0, permitted_rw = 0, permitted_ro = 0, ret = 0;
|
int i = 0, permitted_rw = 0, permitted_ro = 0, ret = 0;
|
||||||
if (ro != 0) {
|
if (ro != 0) {
|
||||||
ro_suffix = ":ro";
|
ro_suffix = ":ro";
|
||||||
@ -1078,9 +1126,8 @@ static int add_mounts(const struct configuration *command_config, const struct c
|
|||||||
ret = 0;
|
ret = 0;
|
||||||
goto free_and_exit;
|
goto free_and_exit;
|
||||||
}
|
}
|
||||||
|
ret = normalize_mounts(permitted_ro_mounts, 1);
|
||||||
ret = normalize_mounts(permitted_ro_mounts);
|
ret |= normalize_mounts(permitted_rw_mounts, 1);
|
||||||
ret |= normalize_mounts(permitted_rw_mounts);
|
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
fprintf(ERRORFILE, "Unable to find permitted docker mounts on disk\n");
|
fprintf(ERRORFILE, "Unable to find permitted docker mounts on disk\n");
|
||||||
ret = MOUNT_ACCESS_ERROR;
|
ret = MOUNT_ACCESS_ERROR;
|
||||||
@ -1108,7 +1155,7 @@ static int add_mounts(const struct configuration *command_config, const struct c
|
|||||||
goto free_and_exit;
|
goto free_and_exit;
|
||||||
} else {
|
} else {
|
||||||
// determine if the user can modify the container-executor.cfg file
|
// determine if the user can modify the container-executor.cfg file
|
||||||
tmp_path_buffer[0] = normalize_mount(mount_src);
|
tmp_path_buffer[0] = normalize_mount(mount_src, 0);
|
||||||
// just re-use the function, flip the args to check if the container-executor path is in the requested
|
// just re-use the function, flip the args to check if the container-executor path is in the requested
|
||||||
// mount point
|
// mount point
|
||||||
ret = check_mount_permitted(tmp_path_buffer, container_executor_cfg_path);
|
ret = check_mount_permitted(tmp_path_buffer, container_executor_cfg_path);
|
||||||
|
@ -551,13 +551,16 @@ namespace ContainerExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestDockerUtil, test_check_mount_permitted) {
|
TEST_F(TestDockerUtil, test_check_mount_permitted) {
|
||||||
const char *permitted_mounts[] = {"/etc", "/usr/bin/cut", "/tmp/", NULL};
|
const char *permitted_mounts[] = {"/etc", "/usr/bin/cut", "/tmp/", "regex:nvidia_driver.*", NULL};
|
||||||
std::vector<std::pair<std::string, int> > test_data;
|
std::vector<std::pair<std::string, int> > test_data;
|
||||||
test_data.push_back(std::make_pair<std::string, int>("/etc", 1));
|
test_data.push_back(std::make_pair<std::string, int>("/etc", 1));
|
||||||
test_data.push_back(std::make_pair<std::string, int>("/etc/", 1));
|
test_data.push_back(std::make_pair<std::string, int>("/etc/", 1));
|
||||||
test_data.push_back(std::make_pair<std::string, int>("/etc/passwd", 1));
|
test_data.push_back(std::make_pair<std::string, int>("/etc/passwd", 1));
|
||||||
test_data.push_back(std::make_pair<std::string, int>("/usr/bin/cut", 1));
|
test_data.push_back(std::make_pair<std::string, int>("/usr/bin/cut", 1));
|
||||||
test_data.push_back(std::make_pair<std::string, int>("//usr/", 0));
|
test_data.push_back(std::make_pair<std::string, int>("//usr/", 0));
|
||||||
|
test_data.push_back(std::make_pair<std::string, int>("nvidia_driver_375.66", 1));
|
||||||
|
test_data.push_back(std::make_pair<std::string, int>("nvidia_local_driver", 0));
|
||||||
|
test_data.push_back(std::make_pair<std::string, int>("^/usr/.*$", -1));
|
||||||
test_data.push_back(std::make_pair<std::string, int>("/etc/random-file", -1));
|
test_data.push_back(std::make_pair<std::string, int>("/etc/random-file", -1));
|
||||||
|
|
||||||
std::vector<std::pair<std::string, int> >::const_iterator itr;
|
std::vector<std::pair<std::string, int> >::const_iterator itr;
|
||||||
@ -568,9 +571,9 @@ namespace ContainerExecutor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
TEST_F(TestDockerUtil, test_normalize_mounts) {
|
TEST_F(TestDockerUtil, test_normalize_mounts) {
|
||||||
const int entries = 4;
|
const int entries = 5;
|
||||||
const char *permitted_mounts[] = {"/home", "/etc", "/usr/bin/cut", NULL};
|
const char *permitted_mounts[] = {"/home", "/etc", "/usr/bin/cut", "regex:/dev/nvidia.*", NULL};
|
||||||
const char *expected[] = {"/home/", "/etc/", "/usr/bin/cut", NULL};
|
const char *expected[] = {"/home/", "/etc/", "/usr/bin/cut", "regex:/dev/nvidia.*", NULL};
|
||||||
char **ptr = static_cast<char **>(malloc(entries * sizeof(char *)));
|
char **ptr = static_cast<char **>(malloc(entries * sizeof(char *)));
|
||||||
for (int i = 0; i < entries; ++i) {
|
for (int i = 0; i < entries; ++i) {
|
||||||
if (permitted_mounts[i] != NULL) {
|
if (permitted_mounts[i] != NULL) {
|
||||||
@ -579,7 +582,7 @@ namespace ContainerExecutor {
|
|||||||
ptr[i] = NULL;
|
ptr[i] = NULL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
normalize_mounts(ptr);
|
normalize_mounts(ptr, 1);
|
||||||
for (int i = 0; i < entries; ++i) {
|
for (int i = 0; i < entries; ++i) {
|
||||||
ASSERT_STREQ(expected[i], ptr[i]);
|
ASSERT_STREQ(expected[i], ptr[i]);
|
||||||
}
|
}
|
||||||
@ -742,7 +745,7 @@ namespace ContainerExecutor {
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
std::string container_executor_cfg_contents = "[docker]\n"
|
std::string container_executor_cfg_contents = "[docker]\n"
|
||||||
" docker.privileged-containers.registries=hadoop\n"
|
" docker.privileged-containers.registries=hadoop\n"
|
||||||
" docker.allowed.devices=/dev/test-device,/dev/device2";
|
" docker.allowed.devices=/dev/test-device,/dev/device2,regex:/dev/nvidia.*,regex:/dev/gpu-uvm.*";
|
||||||
std::vector<std::pair<std::string, std::string> > file_cmd_vec;
|
std::vector<std::pair<std::string, std::string> > file_cmd_vec;
|
||||||
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
||||||
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n devices=/dev/test-device:/dev/test-device",
|
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n devices=/dev/test-device:/dev/test-device",
|
||||||
@ -754,6 +757,14 @@ namespace ContainerExecutor {
|
|||||||
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n"
|
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n"
|
||||||
" devices=/dev/test-device:/dev/test-device,/dev/device2:/dev/device2",
|
" devices=/dev/test-device:/dev/test-device,/dev/device2:/dev/device2",
|
||||||
"--device='/dev/test-device:/dev/test-device' --device='/dev/device2:/dev/device2' "));
|
"--device='/dev/test-device:/dev/test-device' --device='/dev/device2:/dev/device2' "));
|
||||||
|
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
||||||
|
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n"
|
||||||
|
"devices=/dev/nvidiactl:/dev/nvidiactl",
|
||||||
|
"--device='/dev/nvidiactl:/dev/nvidiactl' "));
|
||||||
|
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
||||||
|
"[docker-command-execution]\n docker-command=run\n image=hadoop/image\n"
|
||||||
|
"devices=/dev/nvidia1:/dev/nvidia1,/dev/gpu-uvm-tools:/dev/gpu-uvm-tools",
|
||||||
|
"--device='/dev/nvidia1:/dev/nvidia1' --device='/dev/gpu-uvm-tools:/dev/gpu-uvm-tools' "));
|
||||||
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
file_cmd_vec.push_back(std::make_pair<std::string, std::string>(
|
||||||
"[docker-command-execution]\n docker-command=run\n image=hadoop/image", ""));
|
"[docker-command-execution]\n docker-command=run\n image=hadoop/image", ""));
|
||||||
write_container_executor_cfg(container_executor_cfg_contents);
|
write_container_executor_cfg(container_executor_cfg_contents);
|
||||||
@ -804,6 +815,36 @@ namespace ContainerExecutor {
|
|||||||
ASSERT_EQ(INVALID_DOCKER_DEVICE, ret);
|
ASSERT_EQ(INVALID_DOCKER_DEVICE, ret);
|
||||||
ASSERT_EQ(0, strlen(buff));
|
ASSERT_EQ(0, strlen(buff));
|
||||||
|
|
||||||
|
write_command_file("[docker-command-execution]\n docker-command=run\n image=hadoop/image\n devices=/dev/testnvidia:/dev/testnvidia");
|
||||||
|
ret = read_config(docker_command_file.c_str(), &cmd_cfg);
|
||||||
|
if (ret != 0) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
strcpy(buff, "test string");
|
||||||
|
ret = set_devices(&cmd_cfg, &container_cfg, buff, buff_len);
|
||||||
|
ASSERT_EQ(INVALID_DOCKER_DEVICE, ret);
|
||||||
|
ASSERT_EQ(0, strlen(buff));
|
||||||
|
|
||||||
|
write_command_file("[docker-command-execution]\n docker-command=run\n image=hadoop/image\n devices=/dev/gpu-nvidia-uvm:/dev/gpu-nvidia-uvm");
|
||||||
|
ret = read_config(docker_command_file.c_str(), &cmd_cfg);
|
||||||
|
if (ret != 0) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
strcpy(buff, "test string");
|
||||||
|
ret = set_devices(&cmd_cfg, &container_cfg, buff, buff_len);
|
||||||
|
ASSERT_EQ(INVALID_DOCKER_DEVICE, ret);
|
||||||
|
ASSERT_EQ(0, strlen(buff));
|
||||||
|
|
||||||
|
write_command_file("[docker-command-execution]\n docker-command=run\n image=hadoop/image\n devices=/dev/device1");
|
||||||
|
ret = read_config(docker_command_file.c_str(), &cmd_cfg);
|
||||||
|
if (ret != 0) {
|
||||||
|
FAIL();
|
||||||
|
}
|
||||||
|
strcpy(buff, "test string");
|
||||||
|
ret = set_devices(&cmd_cfg, &container_cfg, buff, buff_len);
|
||||||
|
ASSERT_EQ(INVALID_DOCKER_DEVICE, ret);
|
||||||
|
ASSERT_EQ(0, strlen(buff));
|
||||||
|
|
||||||
container_executor_cfg_contents = "[docker]\n";
|
container_executor_cfg_contents = "[docker]\n";
|
||||||
write_container_executor_cfg(container_executor_cfg_contents);
|
write_container_executor_cfg(container_executor_cfg_contents);
|
||||||
ret = read_config(container_executor_cfg_file.c_str(), &container_cfg);
|
ret = read_config(container_executor_cfg_file.c_str(), &container_cfg);
|
||||||
|
Loading…
Reference in New Issue
Block a user