From e7f7df02df3dea1e8cf93877c47df33c87c1c0de Mon Sep 17 00:00:00 2001 From: jonathanm-12 <81660305+jonathanm-12@users.noreply.github.com> Date: Wed, 6 Dec 2023 16:16:11 -0500 Subject: [PATCH] update env tests for textbook --- content/env_tests/Environment_Tests.ipynb | 324 ++++++++++++---------- 1 file changed, 179 insertions(+), 145 deletions(-) diff --git a/content/env_tests/Environment_Tests.ipynb b/content/env_tests/Environment_Tests.ipynb index 6042ba6..9f44d63 100644 --- a/content/env_tests/Environment_Tests.ipynb +++ b/content/env_tests/Environment_Tests.ipynb @@ -9,21 +9,35 @@ "source": [ "# Environment_Tests\n", "\n", - "These tests are grouped into a single notebook called `all_tests.ipynb` which you can run using the `ope test` command. Running this command outputs the results of the tests, separating them into two groups of `PASSED TESTS` and `FAILED TESTS`. We keep track of these groups using the global variables `ERRORS` and `PASSES` which are initialized below." + "These tests are grouped into a single notebook called `all_tests.ipynb` which you can run using the `ope test` command. Running this command outputs the results of the tests, separating them into two groups of `PASSED TESTS` and `FAILED TESTS`. We keep track of these groups using the global variables `ERRORS` and `PASSES` which are initialized at the last cell. \n", + "We also use the helper function `runshell` to verify that any shell commands being tested do not run into any exceptions." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Helper function for shell commands" ] }, { "cell_type": "code", - "execution_count": 3, - "id": "efa71a7e-855f-43f5-b898-f49b814f23d5", - "metadata": { - "tags": [] - }, + "execution_count": null, + "metadata": {}, "outputs": [], "source": [ - "# global var to keep track of test results we have\n", - "ERRORS = []\n", - "PASSES = []" + "import subprocess\n", + "\n", + "# function to execute shell commands (this is for convenience)\n", + "def runshell(CMD):\n", + " global ERRORS\n", + " try:\n", + " result = subprocess.check_output(CMD, shell=True, stderr=subprocess.STDOUT)\n", + " return 0, result.decode('utf-8')\n", + " except subprocess.CalledProcessError as e:\n", + " return e.returncode, e.output.decode('utf-8')\n", + " except Exception as e: # new add: handle exceptions \n", + " return -1, str(e)" ] }, { @@ -33,7 +47,9 @@ "user_expressions": [] }, "source": [ - "### Write Permission to Home Directory Test" + "### Write Permission to Home Directory Test\n", + "\n", + "This test confirms that the notebook user has permission to write, read, and execute files within their `/home` directory" ] }, { @@ -45,28 +61,19 @@ }, "outputs": [], "source": [ - "import subprocess\n", - "import os\n", - "def shelltest(CMD):\n", - " global ERRORS\n", - " try:\n", - " result = subprocess.check_output(CMD, shell=True, stderr=subprocess.STDOUT)\n", - " return 0, result.decode('utf-8')\n", - " except subprocess.CalledProcessError as e:\n", - " return e.returncode, e.output.decode('utf-8')\n", - " except Exception as e: # new add: handle exceptions \n", - " return -1, str(e)\n", - "\n", "# Test to check write permissions to home directory\n", - "TEST = \"WRITE PERMISSION TO HOME DIRECTORY\"\n", - "CMD = f\"touch {os.path.expanduser('~')}/test_write_permissions.tmp && echo 'Write Permission: Yes' && rm {os.path.expanduser('~')}/test_write_permissions.tmp || echo 'Write Permission: No'\"\n", + "def WritePermissionTest():\n", "\n", - "# Execute Test\n", - "e, output = shelltest(CMD)\n", - "if e == 0:\n", - " PASSES.append(\"Write Permission to Home Directory test\")\n", - "else:\n", - " ERRORS.append(output)" + " TEST = \"WRITE PERMISSION TO HOME DIRECTORY\"\n", + " CMD = f\"touch {os.path.expanduser('~')}/test_write_permissions.tmp && echo 'Write Permission: Yes' && rm {os.path.expanduser('~')}/test_write_permissions.tmp || echo 'Write Permission: No'\"\n", + "\n", + " e, output = runshell(CMD)\n", + "\n", + " if e == 0:\n", + " PASSES.append(\"Write Permission to Home Directory test\")\n", + "\n", + " else:\n", + " ERRORS.append(output)\n" ] }, { @@ -88,54 +95,76 @@ "metadata": {}, "outputs": [], "source": [ - "import os\n", - "\n", "#Verify environment variables are correct\n", "\n", - "NB_UID = int(os.environ['NB_UID'])\n", - "NB_GID = int(os.environ['NB_GID'])\n", - "NB_GROUP = os.environ['NB_GROUP']\n", - "\n", - "XDG_CACHE_HOME = f\"/home/{NB_USER}/.cache/\"\n", + "def check_environment_test():\n", + " \n", + " NB_UID = int(os.environ['NB_UID'])\n", + " NB_GID = int(os.environ['NB_GID'])\n", + " NB_GROUP = os.environ['NB_GROUP']\n", + " NB_USER = os.environ['NB_USER']\n", "\n", - "UID_LOWER_BOUND = 2000 \n", - "UID_UPPER_BOUND = 60000 \n", + " XDG_CACHE_HOME = f\"/home/{NB_USER}/.cache/\"\n", "\n", - "GID_LOWER_BOUND = 2000 \n", - "GID_UPPER_BOUND = 60000 \n", + " UID_LOWER_BOUND = 1000 \n", + " UID_UPPER_BOUND = 1000810001 \n", "\n", - "EXPECTED_NB_GROUP = 'root'\n", + " GID_LOWER_BOUND = 0 \n", + " GID_UPPER_BOUND = 10000 \n", "\n", - "err = []\n", + " EXPECTED_NB_GROUP = 'root'\n", "\n", - "def check_environment_test():\n", + " err = [] \n", + " \n", " if not (UID_LOWER_BOUND <= NB_UID <= UID_UPPER_BOUND):\n", " err.append(f\"NB_UID {NB_UID} is not within the acceptable range: {UID_LOWER_BOUND}-{UID_UPPER_BOUND}.\")\n", - "\n", + " err.append(\"Acceptable ranges can be modified within: \" + os.getcwd()) \n", "\n", " if not (GID_LOWER_BOUND <= NB_GID <= GID_UPPER_BOUND):\n", " err.append(f\"NB_GID {NB_GID} is not within the acceptable range: {GID_LOWER_BOUND}-{GID_UPPER_BOUND}.\")\n", - "\n", - "\n", + " err.append(\"Acceptable ranges can be modified within: \" + os.getcwd()) \n", + " \n", " if NB_GROUP != EXPECTED_NB_GROUP:\n", " err.append(\"NB_GROUP does not match \" + \"'\" + EXPECTED_NB_GROUP + \"'\")\n", "\n", - "\n", " if NB_USER != 'jovyan':\n", " err.append(\"NB_USER does not match 'jovyan'.\")\n", "\n", - "\n", " if XDG_CACHE_HOME != '/home/jovyan/.cache/':\n", " err.append(\"XDD_CACHE_HOME does not match expected path: /home/jovyan/.cache/\")\n", "\n", + " s = '\\n '.join(err)\n", + " if not s: # no errors\n", + " PASSES.append(\"Environmental Variables test\")\n", + " else:\n", + " ERRORS.append(f\"Environmental Variables test:\\n {s}\")\n", + " " + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### ASLR Test\n", "\n", - " if len(err) != 0:\n", - " err.append(\"Environmental Variables test\")\n", - "\n", + "In certain classes, we want to disable ASLR so that students can use GDB properly in their environments. This will help us verify the status of ASLR." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# run gdb 100 times on date binary stopping at first instruction and grep ld. If ld is found at same address (following \n", + "# command has a value of 1 then ASLR is properly disabled.\n", + "def ASLRtest():\n", + " e, count = runshell(\"echo $(for ((i=0;i<100; i++)); do gdb -ex starti -ex quit -q --batch /usr/bin/date 2>/dev/null | grep ld; done | tee out | uniq | wc -l)\")\n", + " if count == 1: \n", + " PASSES.append(\"ASLR test\")\n", " else:\n", - " s = '; '.join(err)\n", - " print(\"Environmental Variables test ERROR: \" + s)\n", - " " + " e2, output2 = runshell(\"echo $(cat /proc/sys/kernel/randomize_va_space)\")\n", + " ERRORS.append(f\"ASLR test ERROR: ASLR status is enabled with status {output2[0]} (0 is disabled, 1 is partial and 2 is full)\")" ] }, { @@ -159,12 +188,12 @@ "outputs": [], "source": [ "# Curl test to check internet connectivity\n", - "e1, output = shelltest(\"curl google.com\")\n", - "e2, output = shelltest(\"ping google.com\")\n", - "if e1 == 0 or e2 == 0:\n", - " PASSES.append(\"Network test\")\n", - "else:\n", - " ERRORS.append(\"Network test ERROR: \" + output)" + "def NetworkTest():\n", + " e, output = runshell(\"curl google.com\")\n", + " if e == 0:\n", + " PASSES.append(\"Network test\")\n", + " else:\n", + " ERRORS.append(\"Network test ERROR: \" + output)" ] }, { @@ -174,7 +203,9 @@ "user_expressions": [] }, "source": [ - "### Pip-Conda Test" + "### Pip-Conda Test\n", + "\n", + "Ensures that the user can install Python-based packages using `pip`" ] }, { @@ -184,14 +215,15 @@ "metadata": {}, "outputs": [], "source": [ - "TEST = \"PIP PACKAGE INSTALLATION\"\n", - "CMD = \"pip install --user pytest\"\n", - "e, output = shelltest(CMD)\n", + "def PipCondaTest():\n", + " TEST = \"PIP PACKAGE INSTALLATION\"\n", + " CMD = \"pip install --user pytest\"\n", + " e, output = runshell(CMD)\n", "\n", - "if e == 0:\n", - " PASSES.append(\"Pip-Conda test\")\n", - "else:\n", - " ERRORS.append(\"Pip-Conda test ERROR: \" + output)" + " if e == 0:\n", + " PASSES.append(\"Pip-Conda test\")\n", + " else:\n", + " ERRORS.append(\"Pip-Conda test: \" + output)" ] }, { @@ -201,37 +233,34 @@ "user_expressions": [] }, "source": [ - "### Git and SSH Test" + "### Git and SSH Test\n", + "\n", + "Tests if Git and SSH configuration files are permanent and working" ] }, { "cell_type": "code", - "execution_count": 28, + "execution_count": 1, "id": "87d00fc1-092d-467f-9760-18b536cab586", "metadata": {}, "outputs": [], "source": [ - "e, output = shelltest(\"readlink -f ~/.gitconfig\")\n", + "def GitSSHTest():\n", "\n", - "if e == 0:\n", - " PASSES.append(\"Git config test\")\n", - "else:\n", - " ERRORS.append(\"Git config test ERROR\" + output)" - ] - }, - { - "cell_type": "code", - "execution_count": 29, - "id": "e6bac3c7-2bc8-4d19-ab0a-903e2b380324", - "metadata": {}, - "outputs": [], - "source": [ - "e, output = shelltest(\"readlink -f /etc/ssh/ssh_config\")\n", + " e, output = runshell(\"readlink -f ~/.gitconfig\")\n", "\n", - "if e == 0:\n", - " PASSES.append(\"ssh config test\")\n", - "else:\n", - " ERRORS.append(\"ssh config test ERROR\" + output)" + " if e == 0:\n", + " PASSES.append(\"Git config test\")\n", + " else:\n", + " ERRORS.append(\"Git config test:\" + output)\n", + "\n", + " e, output = runshell(\"readlink -f /etc/ssh/ssh_config\")\n", + "\n", + " if e == 0:\n", + " PASSES.append(\"ssh config test\")\n", + "\n", + " else:\n", + " ERRORS.append(\"ssh config test:\" + output)" ] }, { @@ -241,7 +270,9 @@ "user_expressions": [] }, "source": [ - "### Conda Directory Test" + "### Conda Directory Test\n", + "\n", + "Test if conda directories are read/writable" ] }, { @@ -251,57 +282,63 @@ "metadata": {}, "outputs": [], "source": [ - "def check_permissions(dir_path):\n", - " \"\"\"Check if a directory is readable and writable.\"\"\"\n", - " global ERRORS\n", - " try:\n", - " readable = os.access(dir_path, os.R_OK)\n", - " writable = os.access(dir_path, os.W_OK)\n", - " return readable, writable\n", - " except Exception as e:\n", - " ERRORS.append(\"Conda directory r/w test\" + f\"checking permissions for {dir_path}.\")\n", - " return False, False\n", - "\n", - "# Identify the conda directories\n", - "conda_base_dir = os.path.abspath(os.path.join(os.path.dirname(os.sys.executable), \"..\"))\n", - "conda_env_dir = os.environ.get('CONDA_PREFIX', '')\n", - "\n", - "unaccessible_dirs = 0\n", - "\n", - "for dir_name, dir_path in [('Conda Base Directory', conda_base_dir), ('Conda Environment Directory', conda_env_dir)]:\n", - " readable, writable = check_permissions(dir_path)\n", - "\n", - " if not (readable and writable):\n", - " unaccessible_dirs += 1\n", - "if unaccessible_dirs == 0:\n", - " PASSES.append(\"Conda directory r/w test\")" + "def check_permissions():\n", + " \n", + " #Identify conda directories\n", + "\n", + " conda_base_dir = os.path.abspath(os.path.join(os.path.dirname(os.sys.executable), \"..\"))\n", + " conda_env_dir = os.environ.get('CONDA_PREFIX', '')\n", + "\n", + " unaccessible_dirs = 0 \n", + " \n", + " for dir_name, dir_path in [('Conda Base Directory', conda_base_dir), ('Conda Environment Directory', conda_env_dir)]:\n", + " try:\n", + " readable = os.access(dir_path, os.R_OK)\n", + " writable = os.access(dir_path, os.W_OK)\n", + " if not (readable and writable):\n", + " unaccessible_dirs += 1\n", + " ERRORS.append(f\"Conda Directory test ERROR: {dir_name} at '{dir_path}' is not readable/writable\")\n", + " except Exception as e:\n", + " ERRORS.append(f\"Conda Directory test ERROR: Error checking permissions for {dir_name} at {dir_path}: {e}\")\n", + " unaccessible_dirs += 1\n", + "\n", + " if unaccessible_dirs == 0:\n", + " PASSES.append(\"Conda directory r/w test passed\")\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Running the tests\n", + "\n", + "The final cell where each test is called. `ERRORS` and `PASSES` are arrays used to contain the results of the tests." ] }, { "cell_type": "code", - "execution_count": 31, + "execution_count": null, "id": "52528d0e-1b7b-4565-a4a6-a47eba0086b7", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "FAILED TESTS:\n", - "-ASLR test ERROR: ASLR status is enabled with status 2 (0 is disabled, 1 is partial and 2 is full)\n", - "-Network test ERROR: /bin/sh: 1: curl: not found\n", - "\n", - "\n", - "\n", - "PASSED TESTS:\n", - "-Write Permission to Home Directory test\n", - "-Pip-Conda test\n", - "-Git config test\n", - "-ssh config test\n" - ] - } - ], + "outputs": [], "source": [ + "import os\n", + "\n", + "# global var to keep track of test results we have\n", + "ERRORS = []\n", + "PASSES = []\n", + "\n", + "\n", + "#Run the tests\n", + "\n", + "WritePermissionTest()\n", + "check_environment_test()\n", + "ASLRtest()\n", + "NetworkTest()\n", + "GitSSHTest()\n", + "check_permissions()\n", + "\n", + "#Print the results\n", "if len(ERRORS) > 0:\n", " print(\"FAILED TESTS:\")\n", " print('-' + '\\n-'.join(ERRORS))\n", @@ -330,23 +367,20 @@ "source": [ "# Define the test and conditions\n", "\n", - "test_command = \"\"\n", - "expected_result = \"\"\n", - "environment_variables = \"\"\n", + "def perform_test():\n", "\n", - "def perform_test(command, expected_result):\n", + " test_command = \"\"\n", + " expected_result = \"\"\n", + " environment_variables = \"\"\n", "\n", - " execution_result, output = shelltest(command)\n", + " execution_result, output = runshell(test_command)\n", "\n", " if output == expected_result:\n", " # If test passes, add to the PASSES array \n", " PASSES.append(\" PASSED\")\n", " else:\n", " # If test fails, add to the ERRORS array with error message\n", - " ERRORS.append(\" FAILED: \" + output)\n", - "\n", - "# Run test to verify success\n", - "perform_test(test_command, expected_result)\n" + " ERRORS.append(\" FAILED: \" + output)\n" ] }, { @@ -376,7 +410,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.9.13" } }, "nbformat": 4,