diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c index 5d138f3..47f2c9b 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.c @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -79,6 +80,11 @@ static const char* TC_READ_STATS_OPTS [] = { "-s", "-b", NULL}; //struct to store the user details struct passwd *user_detail = NULL; +//Docker container related constants. +static const char* DOCKER_CONTAINER_NAME_PREFIX = "container_"; +static const char* DOCKER_CLIENT_CONFIG_ARG = "--config="; +static const char* DOCKER_PULL_COMMAND = "pull"; + FILE* LOGFILE = NULL; FILE* ERRORFILE = NULL; @@ -1208,6 +1214,22 @@ char** tokenize_docker_command(const char *input, int *split_counter) { return linesplit; } +int validate_docker_image_name(const char *image_name) { + regex_t regex; + char *regex_str = "^(([a-zA-Z0-9.-]+)(:[0-9]+)?/)?([a-z0-9_./-]+)(:[a-zA-Z0-9_.-]+)?$"; + int regex_match; + if (regcomp(®ex, regex_str, REG_EXTENDED|REG_NOSUB) != 0) { + printf("Unable to compile regex: %s\n", regex_str); + exit(ERROR_COMPILING_REGEX); + } + regex_match = regexec(®ex, image_name, (size_t) 0, NULL, 0); + regfree(®ex); + if(regex_match == 0) { + return 0; + } + return 1; +} + char* sanitize_docker_command(const char *line) { static struct option long_options[] = { {"name", required_argument, 0, 'n' }, @@ -1221,6 +1243,7 @@ char* sanitize_docker_command(const char *line) { {"cap-drop", required_argument, 0, 'o' }, {"device", required_argument, 0, 'i' }, {"detach", required_argument, 0, 't' }, + {"format", required_argument, 0, 'f' }, {0, 0, 0, 0} }; @@ -1239,6 +1262,35 @@ char* sanitize_docker_command(const char *line) { if(output == NULL) { exit(OUT_OF_MEMORY); } + + // Handle docker client config option. + if(strncmp(linesplit[0], DOCKER_CLIENT_CONFIG_ARG, strlen(DOCKER_CLIENT_CONFIG_ARG)) == 0) { + strcat(output, linesplit[0]); + strcat(output, " "); + long index = 0; + while(index < split_counter) { + linesplit[index] = linesplit[index + 1]; + if (linesplit[index] == NULL) { + split_counter--; + break; + } + index++; + } + } + + // Handle docker pull and image name validation. + if (strncmp(linesplit[0], DOCKER_PULL_COMMAND, strlen(DOCKER_PULL_COMMAND)) == 0) { + if (validate_docker_image_name(linesplit[1]) != 0) { + fprintf(ERRORFILE, "Invalid Docker image name, exiting."); + fflush(ERRORFILE); + exit(DOCKER_IMAGE_INVALID); + } + strcat(output, linesplit[0]); + strcat(output, " "); + strcat(output, linesplit[1]); + return output; + } + strcat(output, linesplit[0]); strcat(output, " "); optind = 1; @@ -1283,6 +1335,11 @@ char* sanitize_docker_command(const char *line) { case 't': quote_and_append_arg(&output, &output_size, "--detach=", optarg); break; + case 'f': + strcat(output, "--format="); + strcat(output, optarg); + strcat(output, " "); + break; default: fprintf(LOGFILE, "Unknown option in docker command, character %d %c, optionindex = %d\n", c, c, optind); fflush(LOGFILE); @@ -1293,7 +1350,11 @@ char* sanitize_docker_command(const char *line) { if(optind < split_counter) { while(optind < split_counter) { - quote_and_append_arg(&output, &output_size, "", linesplit[optind++]); + if (strncmp(linesplit[optind], DOCKER_CONTAINER_NAME_PREFIX, strlen(DOCKER_CONTAINER_NAME_PREFIX)) == 0) { + strcat(output, linesplit[optind++]); + } else { + quote_and_append_arg(&output, &output_size, "", linesplit[optind++]); + } } } @@ -1325,7 +1386,6 @@ char* parse_docker_command_file(const char* command_file) { exit(ERROR_SANITIZING_DOCKER_COMMAND); } fprintf(LOGFILE, "Using command %s\n", ret); - fflush(LOGFILE); return ret; } diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h index e40bd90..c1f2f29 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/impl/container-executor.h @@ -74,7 +74,9 @@ enum errorcodes { COULD_NOT_CREATE_APP_LOG_DIRECTORIES = 36, COULD_NOT_CREATE_TMP_DIRECTORIES = 37, ERROR_CREATE_CONTAINER_DIRECTORIES_ARGUMENTS = 38, - ERROR_SANITIZING_DOCKER_COMMAND = 39 + ERROR_SANITIZING_DOCKER_COMMAND = 39, + DOCKER_IMAGE_INVALID = 40, + ERROR_COMPILING_REGEX = 41 }; enum operations { @@ -309,3 +311,8 @@ int run_docker(const char *command_file); * Sanitize docker commands. Returns NULL if there was any failure. */ char* sanitize_docker_command(const char *line); + +/** + * Validate the docker image name matches the expected input. return 0 on success. + */ +int validate_docker_image_name(const char *image_name); diff --git a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/test-container-executor.c b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/test-container-executor.c index 83d11ec..5347e0e 100644 --- a/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/test-container-executor.c +++ b/hadoop-yarn-project/hadoop-yarn/hadoop-yarn-server/hadoop-yarn-server-nodemanager/src/main/native/container-executor/test/test-container-executor.c @@ -1092,7 +1092,13 @@ void test_sanitize_docker_command() { "run --name=$CID --user=nobody -d --workdir=/yarn/local/cdir --privileged --rm --device=/sys/fs/cgroup/device:/sys/fs/cgroup/device --detach=true --cgroup-parent=/sys/fs/cgroup/cpu/yarn/cid --net=host --cap-drop=ALL --cap-add=SYS_CHROOT --cap-add=MKNOD --cap-add=SETFCAP --cap-add=SETPCAP --cap-add=FSETID --cap-add=CHOWN --cap-add=AUDIT_WRITE --cap-add=SETGID --cap-add=NET_RAW --cap-add=FOWNER --cap-add=SETUID --cap-add=DAC_OVERRIDE --cap-add=KILL --cap-add=NET_BIND_SERVICE -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /yarn/local/cdir:/yarn/local/cdir -v /yarn/local/usercache/test/:/yarn/local/usercache/test/ ubuntu bash /yarn/local/usercache/test/appcache/aid/cid/launch_container.sh", "run --name=cname --user=nobody -d --workdir=/yarn/local/cdir --privileged --rm --device=/sys/fs/cgroup/device:/sys/fs/cgroup/device --detach=true --cgroup-parent=/sys/fs/cgroup/cpu/yarn/cid --net=host --cap-drop=ALL --cap-add=SYS_CHROOT --cap-add=MKNOD --cap-add=SETFCAP --cap-add=SETPCAP --cap-add=FSETID --cap-add=CHOWN --cap-add=AUDIT_WRITE --cap-add=SETGID --cap-add=NET_RAW --cap-add=FOWNER --cap-add=SETUID --cap-add=DAC_OVERRIDE --cap-add=KILL --cap-add=NET_BIND_SERVICE -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /yarn/local/cdir:/yarn/local/cdir -v /yarn/local/usercache/test/:/yarn/local/usercache/test/ ubuntu || touch /tmp/file # bash /yarn/local/usercache/test/appcache/aid/cid/launch_container.sh", "run --name=cname --user=nobody -d --workdir=/yarn/local/cdir --privileged --rm --device=/sys/fs/cgroup/device:/sys/fs/cgroup/device --detach=true --cgroup-parent=/sys/fs/cgroup/cpu/yarn/cid --net=host --cap-drop=ALL --cap-add=SYS_CHROOT --cap-add=MKNOD --cap-add=SETFCAP --cap-add=SETPCAP --cap-add=FSETID --cap-add=CHOWN --cap-add=AUDIT_WRITE --cap-add=SETGID --cap-add=NET_RAW --cap-add=FOWNER --cap-add=SETUID --cap-add=DAC_OVERRIDE --cap-add=KILL --cap-add=NET_BIND_SERVICE -v /sys/fs/cgroup:/sys/fs/cgroup:ro -v /yarn/local/cdir:/yarn/local/cdir -v /yarn/local/usercache/test/:/yarn/local/usercache/test/ ubuntu' || touch /tmp/file # bash /yarn/local/usercache/test/appcache/aid/cid/launch_container.sh", - "run ''''''''" + "run ''''''''", + "inspect --format='{{range(.NetworkSettings.Networks)}}{{.IPAddress}},{{end}}{{.Config.Hostname}}' container_e111_1111111111111_1111_01_111111", + "rm container_e111_1111111111111_1111_01_111111", + "stop container_e111_1111111111111_1111_01_111111", + "pull ubuntu", + "pull registry.com/user/ubuntu", + "--config=/yarn/local/cdir/ pull registry.com/user/ubuntu" }; char *expected_output[] = { "run --name='cname' --user='nobody' -d --workdir='/yarn/local/cdir' --privileged --rm --device='/sys/fs/cgroup/device:/sys/fs/cgroup/device' --detach='true' --cgroup-parent='/sys/fs/cgroup/cpu/yarn/cid' --net='host' --cap-drop='ALL' --cap-add='SYS_CHROOT' --cap-add='MKNOD' --cap-add='SETFCAP' --cap-add='SETPCAP' --cap-add='FSETID' --cap-add='CHOWN' --cap-add='AUDIT_WRITE' --cap-add='SETGID' --cap-add='NET_RAW' --cap-add='FOWNER' --cap-add='SETUID' --cap-add='DAC_OVERRIDE' --cap-add='KILL' --cap-add='NET_BIND_SERVICE' -v '/sys/fs/cgroup:/sys/fs/cgroup:ro' -v '/yarn/local/cdir:/yarn/local/cdir' -v '/yarn/local/usercache/test/:/yarn/local/usercache/test/' 'ubuntu' 'bash' '/yarn/local/usercache/test/appcache/aid/cid/launch_container.sh' ", @@ -1100,12 +1106,18 @@ void test_sanitize_docker_command() { "run --name='cname' --user='nobody' -d --workdir='/yarn/local/cdir' --privileged --rm --device='/sys/fs/cgroup/device:/sys/fs/cgroup/device' --detach='true' --cgroup-parent='/sys/fs/cgroup/cpu/yarn/cid' --net='host' --cap-drop='ALL' --cap-add='SYS_CHROOT' --cap-add='MKNOD' --cap-add='SETFCAP' --cap-add='SETPCAP' --cap-add='FSETID' --cap-add='CHOWN' --cap-add='AUDIT_WRITE' --cap-add='SETGID' --cap-add='NET_RAW' --cap-add='FOWNER' --cap-add='SETUID' --cap-add='DAC_OVERRIDE' --cap-add='KILL' --cap-add='NET_BIND_SERVICE' -v '/sys/fs/cgroup:/sys/fs/cgroup:ro' -v '/yarn/local/cdir:/yarn/local/cdir' -v '/yarn/local/usercache/test/:/yarn/local/usercache/test/' 'ubuntu' '||' 'touch' '/tmp/file' '#' 'bash' '/yarn/local/usercache/test/appcache/aid/cid/launch_container.sh' ", "run --name='cname' --user='nobody' -d --workdir='/yarn/local/cdir' --privileged --rm --device='/sys/fs/cgroup/device:/sys/fs/cgroup/device' --detach='true' --cgroup-parent='/sys/fs/cgroup/cpu/yarn/cid' --net='host' --cap-drop='ALL' --cap-add='SYS_CHROOT' --cap-add='MKNOD' --cap-add='SETFCAP' --cap-add='SETPCAP' --cap-add='FSETID' --cap-add='CHOWN' --cap-add='AUDIT_WRITE' --cap-add='SETGID' --cap-add='NET_RAW' --cap-add='FOWNER' --cap-add='SETUID' --cap-add='DAC_OVERRIDE' --cap-add='KILL' --cap-add='NET_BIND_SERVICE' -v '/sys/fs/cgroup:/sys/fs/cgroup:ro' -v '/yarn/local/cdir:/yarn/local/cdir' -v '/yarn/local/usercache/test/:/yarn/local/usercache/test/' 'ubuntu'\"'\"'' '||' 'touch' '/tmp/file' '#' 'bash' '/yarn/local/usercache/test/appcache/aid/cid/launch_container.sh' ", "run ''\"'\"''\"'\"''\"'\"''\"'\"''\"'\"''\"'\"''\"'\"''\"'\"'' ", + "inspect --format='{{range(.NetworkSettings.Networks)}}{{.IPAddress}},{{end}}{{.Config.Hostname}}' container_e111_1111111111111_1111_01_111111", + "rm container_e111_1111111111111_1111_01_111111", + "stop container_e111_1111111111111_1111_01_111111", + "pull ubuntu", + "pull registry.com/user/ubuntu", + "--config=/yarn/local/cdir/ pull registry.com/user/ubuntu" }; int input_size = sizeof(input) / sizeof(char *); int i = 0; for(i = 0; i < input_size; i++) { - char *command = (char *) calloc(strlen(input[i]), sizeof(char)); + char *command = (char *) calloc(strlen(input[i]) + 1 , sizeof(char)); strncpy(command, input[i], strlen(input[i])); char *op = sanitize_docker_command(command); if(strncmp(expected_output[i], op, strlen(expected_output[i])) != 0) { @@ -1116,6 +1128,65 @@ void test_sanitize_docker_command() { } } +void test_validate_docker_image_name() { + + char *good_input[] = { + "ubuntu", + "ubuntu:latest", + "ubuntu:14.04", + "ubuntu:LATEST", + "registry.com:5000/user/ubuntu", + "registry.com:5000/user/ubuntu:latest", + "registry.com:5000/user/ubuntu:0.1.2.3", + "registry.com/user/ubuntu", + "registry.com/user/ubuntu:latest", + "registry.com/user/ubuntu:0.1.2.3", + "registry.com/user/ubuntu:test-image", + "registry.com/user/ubuntu:test_image", + "registry.com/ubuntu", + "user/ubuntu", + "user/ubuntu:0.1.2.3", + "user/ubuntu:latest", + "user/ubuntu:test_image", + "user/ubuntu.test:test_image", + "user/ubuntu-test:test-image", + "registry.com/ubuntu/ubuntu/ubuntu" + }; + + char *bad_input[] = { + "UBUNTU", + "registry.com|5000/user/ubuntu", + "registry.com | 5000/user/ubuntu", + "ubuntu' || touch /tmp/file #", + "ubuntu || touch /tmp/file #", + "''''''''", + "bad_host_name:5000/user/ubuntu", + "registry.com:foo/ubuntu/ubuntu/ubuntu", + "registry.com/ubuntu:foo/ubuntu/ubuntu" + }; + + int good_input_size = sizeof(good_input) / sizeof(char *); + int i = 0; + for(i = 0; i < good_input_size; i++) { + int op = validate_docker_image_name(good_input[i]); + if(op != 0) { + printf("\nFAIL: docker image name %s is invalid", good_input[i]); + exit(1); + } + } + + int bad_input_size = sizeof(bad_input) / sizeof(char *); + int j = 0; + for(j = 0; j < bad_input_size; j++) { + int op = validate_docker_image_name(bad_input[j]); + if(op != 1) { + printf("\nFAIL: docker image name %s is valid, expected invalid", bad_input[j]); + exit(1); + } + } + +} + // This test is expected to be executed either by a regular // user or by root. If executed by a regular user it doesn't // test all the functions that would depend on changing the @@ -1210,6 +1281,9 @@ int main(int argc, char **argv) { printf("\nTesting sanitize docker commands()\n"); test_sanitize_docker_command(); + printf("\nTesting validate_docker_image_name()\n"); + test_validate_docker_image_name(); + test_check_user(0); #ifdef __APPLE__