diff --git a/.github/workflows/testing.yml b/.github/workflows/testing.yml index 44f8d7d48..b7b6a01a9 100644 --- a/.github/workflows/testing.yml +++ b/.github/workflows/testing.yml @@ -147,7 +147,10 @@ jobs: cache-dependency-path: './modules/ui/package-lock.json' - name: Install Chromium Browser - run: sudo apt install chromium-browser + run: | + sudo add-apt-repository ppa:xtradeb/apps -y + sudo apt-get update + sudo apt-get install -y chromium - name: Install dependencies run: npm ci @@ -155,7 +158,9 @@ jobs: - name: Run tests run: | - export CHROME_BIN=/usr/bin/chromium-browser + sudo sysctl -w kernel.unprivileged_userns_clone=1 + export CHROMIUM_FLAGS="--no-sandbox --disable-setuid-sandbox" + export CHROME_BIN=/usr/bin/chromium CI=true npm run test-ci env: CI: true diff --git a/.gitignore b/.gitignore index 9d6cc2acc..772a5356a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ error pylint.out __pycache__/ build/ +local/reports/ # Ignore generated files from unit tests testing/unit_test/temp/ diff --git a/cmd/install b/cmd/install index baf0f5469..e7b0ca81a 100755 --- a/cmd/install +++ b/cmd/install @@ -68,7 +68,7 @@ deactivate cmd/build # Create local folders -mkdir -p local/{devices,root_certs,risk_profiles} +mkdir -p local/{devices,root_certs,risk_profiles,reports} # Set file permissions on local # This does not work on GitHub actions diff --git a/docs/dev/mockoon.json b/docs/dev/mockoon.json index 8e9590e76..c67ca350b 100644 --- a/docs/dev/mockoon.json +++ b/docs/dev/mockoon.json @@ -1,5 +1,5 @@ { - "uuid": "1b2ab06a-28fe-4bb6-9714-4347bff629bd", + "uuid": "a8882b26-c990-47b9-9dfb-49532a5934c2", "lastMigration": 32, "name": "Testrun", "endpointPrefix": "", @@ -9,14 +9,14 @@ "folders": [], "routes": [ { - "uuid": "609cbb2f-b064-4d94-9d47-705f902af57d", + "uuid": "1fe86785-8d24-4abd-b83e-a26022997bcc", "type": "http", "documentation": "Fetch a list of available interfaces", "method": "get", "endpoint": "system/interfaces", "responses": [ { - "uuid": "7a736bd6-7087-4227-91d7-b9efb5d46bc1", + "uuid": "2629cd31-80e2-41eb-800b-c33fe2e48270", "body": "{\n \"enx00e04c020fa8\": \"00:e0:4c:02:0f:a8\",\n \"enx207bd26205e9\": \"20:7b:d2:62:05:e9\"\n}", "latency": 0, "statusCode": 200, @@ -38,14 +38,14 @@ "responseMode": null }, { - "uuid": "32ddc969-bf37-4bd6-a926-329ffa351583", + "uuid": "ea00c773-fb7c-4889-b140-c2b48cae7819", "type": "http", "documentation": "Set the system configuration", "method": "post", "endpoint": "system/config", "responses": [ { - "uuid": "2513112c-7b46-4093-9954-585f9d2b5807", + "uuid": "b05c42b3-bb8a-459e-9c07-efd64065a337", "body": "{\n \"success\": \"Successfully updated the configuration\"\n}", "latency": 0, "statusCode": 200, @@ -79,7 +79,7 @@ "callbacks": [] }, { - "uuid": "46a870dd-a506-4bc3-a8ea-fb823d9ad700", + "uuid": "415311af-1cb9-41c7-88fe-753a5291c9ef", "body": "{\n \"error\": \"Invalid configuration received\"\n}", "latency": 0, "statusCode": 400, @@ -116,15 +116,15 @@ "responseMode": null }, { - "uuid": "24a155d7-8d30-458e-bc03-2240ecc95a30", + "uuid": "6299b6ba-2746-4f44-b7ba-2c31bdd795dc", "type": "http", "documentation": "Fetch a list of available devices", "method": "get", "endpoint": "devices", "responses": [ { - "uuid": "eef8575a-b420-4b56-b879-303b3c58d7d9", - "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB 140\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": false\n },\n \"nmap\": {\n \"enabled\": false\n }\n }\n },\n {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"manufacturer\": \"Manufacturer X\",\n \"model\": \"Device X\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"baseline\": {\n \"enabled\": false\n },\n \"services\": {\n \"enabled\": true\n }\n }\n }\n]", + "uuid": "12eda93b-fd54-427e-9069-dc554da4372e", + "body": "{{data 'cntg'}}", "latency": 0, "statusCode": 200, "label": "", @@ -145,14 +145,14 @@ "responseMode": null }, { - "uuid": "98e836d3-8af1-4321-9cdc-7feba8b40d8e", + "uuid": "cf9a795b-6b3d-41cf-8115-3777a3514dc8", "type": "http", "documentation": "Create a device", "method": "post", "endpoint": "device", "responses": [ { - "uuid": "bee3a25e-ffd7-4a38-a894-26217efb106f", + "uuid": "94f8792a-2116-4d0d-95b2-812095a62fdc", "body": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-SRC\",\n \"mac_addr\": \"00:1e:42:35:73:c2\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": false\n },\n \"services\": {\n \"enabled\": true\n },\n \"dhcp\": {\n \"enabled\": false\n }\n }\n}", "latency": 0, "statusCode": 201, @@ -179,7 +179,7 @@ "callbacks": [] }, { - "uuid": "77c157b9-0175-4c84-b660-cf96e712e450", + "uuid": "6739ac22-3586-4931-b114-cd13a79e1b99", "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}", "latency": 0, "statusCode": 409, @@ -198,7 +198,7 @@ "callbacks": [] }, { - "uuid": "8f021e4c-4d30-4bdd-8e03-04bb136a9970", + "uuid": "ff9e1adb-08fe-4199-97cc-c8d1db756473", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -217,7 +217,7 @@ "callbacks": [] }, { - "uuid": "7d454ec4-61a2-4ede-a5bc-d7634d0d007b", + "uuid": "5ba8b891-58be-4495-83b7-d339bec248f4", "body": "{\n \"error\": \"Invalid JSON received\"\n}", "latency": 0, "statusCode": 400, @@ -239,15 +239,15 @@ "responseMode": null }, { - "uuid": "dcead7f8-71da-43ef-974b-ceddf8f262cc", + "uuid": "1f580148-ed7a-47f4-bc4b-6b3ce5c8d8a2", "type": "http", "documentation": "Start a new Testrun", "method": "post", "endpoint": "system/start", "responses": [ { - "uuid": "1fed2343-e85b-4026-b96c-e024aa4535f5", - "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-SRC\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", + "uuid": "b424d0dd-c528-403d-aa3f-57b329cb4f17", + "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun started successfully", @@ -273,8 +273,8 @@ "callbacks": [] }, { - "uuid": "5ec818f2-562f-4bfa-bf1e-e8055b927bac", - "body": "{\n \"status\": \"Monitoring\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-SRC\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", + "uuid": "25feb13a-302c-47e0-8edd-6e763fde6e2a", + "body": "{\n \"status\": \"Monitoring\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun monitoring device", @@ -300,7 +300,7 @@ "callbacks": [] }, { - "uuid": "ee6e5e58-4fbb-46b3-8c78-2309ec01dfc0", + "uuid": "c41e62a9-62cc-4e62-832a-bbc2694ae216", "body": "{\n \"error\": \"A testrun is already in progress\"\n}", "latency": 0, "statusCode": 409, @@ -319,7 +319,7 @@ "callbacks": [] }, { - "uuid": "b34bc849-66c6-4409-84f0-5418edba7de4", + "uuid": "264a27ca-dceb-46ac-b836-a5309493b1fd", "body": "{\n \"error\": \"No device specified\"\n}", "latency": 0, "statusCode": 400, @@ -346,7 +346,7 @@ "callbacks": [] }, { - "uuid": "f5b7e6ed-1c33-4c63-bbc3-63d251b29fbf", + "uuid": "f892afa2-e75a-4178-9f89-be193523ca40", "body": "{\n \"error\": \"Device not found\"\n}", "latency": 0, "statusCode": 404, @@ -365,7 +365,7 @@ "callbacks": [] }, { - "uuid": "874df7e9-1f62-4fdf-9ec8-100df7d0291f", + "uuid": "caa22759-efd1-4b0e-8594-20ff1ccb2d7e", "body": "{\n \"error\": \"Configured interfaces are not ready for use. Ensure both interfaces are connected.\"\n}", "latency": 0, "statusCode": 500, @@ -387,15 +387,15 @@ "responseMode": null }, { - "uuid": "9a9482ee-09d6-4ab7-9fb0-bbfed791bd9d", + "uuid": "69fe7ba0-1a81-482f-b8a7-f92cb75d957c", "type": "http", "documentation": "Get the current status of Testrun", "method": "get", "endpoint": "system/status", "responses": [ { - "uuid": "a6d2fa8a-2c0e-4915-b119-703776d5982b", - "body": "{\n \"status\": \"In Progress\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",,\n \"required_result\": \"Required\"\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n } \n ]\n }\n}", + "uuid": "cf79f5cb-0c17-4071-865b-39223e155b67", + "body": "{\n \"status\": \"In Progress\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",,\n \"required_result\": \"Required\"\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n } \n ]\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun In Progress", @@ -413,8 +413,8 @@ "callbacks": [] }, { - "uuid": "9a9adf23-be2b-4bb3-bb89-bd3d5ae731a8", - "body": "{\n \"status\": \"Monitoring\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", + "uuid": "d2f96e17-afab-40f5-b743-f6b8ae624277", + "body": "{\n \"status\": \"Monitoring\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun Monitoring", @@ -432,7 +432,7 @@ "callbacks": [] }, { - "uuid": "6b2055fc-3e9a-43cd-8157-92168b1a0df3", + "uuid": "0532ee0c-44c8-4512-a46c-7e51c6135249", "body": "{\n \"status\": \"Idle\",\n \"device\": null,\n \"started\": null,\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", "latency": 0, "statusCode": 200, @@ -446,13 +446,13 @@ "rulesOperator": "OR", "disableTemplating": false, "fallbackTo404": false, - "default": true, + "default": false, "crudKey": "id", "callbacks": [] }, { - "uuid": "6e8b9ee4-9905-480d-974a-ebe0a3778a31", - "body": "{\n \"status\": \"Cancelled\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:24:00.123Z\",\n \"report\": null,\n \"tests\": {\n \"total\": 22,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n }\n ]\n }\n}", + "uuid": "9ff290f0-df49-4da6-a651-ba3a8f38f9b5", + "body": "{\n \"status\": \"Cancelled\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:24:00.123Z\",\n \"report\": null,\n \"tests\": {\n \"total\": 22,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n }\n ]\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun Cancelled", @@ -470,8 +470,8 @@ "callbacks": [] }, { - "uuid": "551c1bca-85e5-4d19-8800-902ddb76c88f", - "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", + "uuid": "8e1faed5-5c59-4813-8643-e464b4292cce", + "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun Waiting for Device", @@ -489,8 +489,8 @@ "callbacks": [] }, { - "uuid": "1a1346d2-d309-43c3-8993-2416c704ef06", - "body": "{\n \"status\": \"Non-Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Non-Compliant\",\n \"required_result\": \"Compliant\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Compliant\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", + "uuid": "7b30be04-6c52-4343-b2ab-b717aebb2268", + "body": "{\n \"status\": \"Non-Compliant\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"/report/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"export\": \"/export/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Non-Compliant\",\n \"required_result\": \"Compliant\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Compliant\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", "latency": 0, "statusCode": 200, "label": "Test Run Non-Compliant", @@ -508,8 +508,8 @@ "callbacks": [] }, { - "uuid": "afe4b3e4-8401-46ea-8850-2dcb919fb7d3", - "body": "{\n \"status\": \"Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", + "uuid": "8cfe2235-264e-4f93-8409-857ffe3a8b11", + "body": "{\n \"status\": \"Compliant\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"/report/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"export\": \"/export/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun Compliant", @@ -522,13 +522,13 @@ "rulesOperator": "OR", "disableTemplating": false, "fallbackTo404": false, - "default": false, + "default": true, "crudKey": "id", "callbacks": [] }, { - "uuid": "16da9b0b-68ac-41bb-b698-8e196ebabd4c", - "body": "{\n \"status\": \"Error\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Error\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", + "uuid": "cc0128af-3b8c-4a0c-a161-13cfd03b16cf", + "body": "{\n \"status\": \"Error\",\n \"device\": {{data 'cntg' '0'}},\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Error\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}", "latency": 0, "statusCode": 200, "label": "Testrun Error", @@ -549,14 +549,14 @@ "responseMode": null }, { - "uuid": "217a5e1b-fda6-4930-9f05-1372bc11590c", + "uuid": "9a013389-fa81-4b39-a60b-d0305eab5f58", "type": "http", "documentation": "Stop the current Testrun", "method": "post", "endpoint": "system/stop", "responses": [ { - "uuid": "84004877-fe78-4817-a542-be1aa23088ec", + "uuid": "c50a8977-5cfe-41e6-beaa-1fc2069d72c6", "body": "{\n \"success\": \"Testrun cancelled successfully\"\n}", "latency": 0, "statusCode": 200, @@ -575,7 +575,7 @@ "callbacks": [] }, { - "uuid": "04561650-fffe-4880-8c69-b94e8b96ba38", + "uuid": "b90b47e8-9f3f-4512-82ec-f98023a00e55", "body": "{\n \"error\": \"Testrun is not currently running\"\n}", "latency": 0, "statusCode": 404, @@ -597,15 +597,15 @@ "responseMode": null }, { - "uuid": "a8ba2fcb-8e05-42f4-9d5f-cea63aa8ead7", + "uuid": "d808737a-e666-4ff4-89d6-7df7da041710", "type": "http", "documentation": "Fetch a list of previous Testruns", "method": "get", "endpoint": "reports", "responses": [ { - "uuid": "6adc954a-55c9-40ed-8f49-cf38f659d883", - "body": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c6\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"protocol\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2024-05-03 12:09:59\",\n \"finished\": \"2024-05-03 12:15:51\",\n \"tests\": {\n \"total\": 20,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet discovery could not resolve any devices\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"No BACnet devices discovered.\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Skipped\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Failed to establish Modbus connection to device\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed.\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\\nTLS 1.3 not validated: Certificate has expired\\nEC key length passed: 256 >= 224\\nDevice certificate has not been signed\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\"\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found.\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required\",\n \"result\": \"Skipped\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/123 123/2024-05-03T12:09:59\",\n \"export\": \"http://localhost:8000/export/123 123/2024-05-03T12:09:59\"\n \n }\n]", + "uuid": "26c16650-9195-4fce-b76f-f00c9192689b", + "body": "[\n {\n \"testrun\": {\n \"version\": \"2.2.2\"\n },\n \"device\": {{data 'cntg' '0'}},\n \"status\": \"Non-Compliant\",\n \"started\": \"2026-02-02 17:24:52\",\n \"finished\": \"2026-02-02 17:34:58\",\n \"report\": \"/report/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"export\": \"/export/f0d4e2f2f541_2026-02-02T17:24:52\",\n \"folder_name\": \"f0d4e2f2f541_2026-02-02T17:24:52\",\n \"delete\": \"/report/f0d4e2f2f541_2026-02-02T17:24:52\"\n },\n {\n \"testrun\": {\n \"version\": \"2.3.2\"\n },\n \"mac_addr\": null,\n \"device\": {{data 'cntg' '0'}},\n \"status\": \"Complete\",\n \"result\": \"Non-Compliant\",\n \"started\": \"2026-06-01 20:53:11\",\n \"finished\": \"2026-06-01 21:05:07\",\n \"report\": \"/report/f0d4e2f2f541_2026-06-01T20:53:11\",\n \"export\": \"/export/f0d4e2f2f541_2026-06-01T20:53:11\",\n \"folder_name\": \"f0d4e2f2f541_2026-06-01T20:53:11\",\n \"delete\": \"/report/f0d4e2f2f541_2026-06-01T20:53:11\"\n }\n]", "latency": 0, "statusCode": 200, "label": "", @@ -626,14 +626,14 @@ "responseMode": null }, { - "uuid": "a218761a-6ac6-466b-932e-9d58b6ec3a2e", + "uuid": "adeda7ee-f6d2-4ab1-ac2a-861d1f2abaed", "type": "http", "documentation": "Delete a device", "method": "delete", "endpoint": "device", "responses": [ { - "uuid": "5afbef24-a245-4ca2-b41d-6d867120f179", + "uuid": "72e34cf8-eae0-4aca-bf21-8c98939d83c8", "body": "{\n \"success\": \"Successfully deleted the device\"\n}", "latency": 0, "statusCode": 200, @@ -660,7 +660,7 @@ "callbacks": [] }, { - "uuid": "7618b710-3528-467f-8271-b6ee12f69a88", + "uuid": "9c2bb5c2-476a-4606-b0f8-79198d0467f6", "body": "{\n \"error\": \"Device not found\"\n}", "latency": 0, "statusCode": 404, @@ -679,7 +679,7 @@ "callbacks": [] }, { - "uuid": "491a490a-f9e3-457e-887f-f508d088c6c8", + "uuid": "737af397-b751-4322-b784-df67013d0ad0", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -706,7 +706,7 @@ "callbacks": [] }, { - "uuid": "85843fbf-2b63-41ab-9836-51bdc0a31b8e", + "uuid": "054dc178-5f98-4cb9-bbd4-adca99951e9c", "body": "{\n \"error\": \"Cannot delete this device whilst it is being tested\"\n}", "latency": 0, "statusCode": 403, @@ -728,14 +728,14 @@ "responseMode": null }, { - "uuid": "fd6fc843-4a77-4532-ba38-f81cdf345dc2", + "uuid": "f3b4ed0b-11e2-4d7f-8d08-4b9840c70d5c", "type": "http", "documentation": "Get the current system configuration", "method": "get", "endpoint": "system/config", "responses": [ { - "uuid": "b78733cb-15e5-48ba-8ac1-34eb04dbaa28", + "uuid": "e6bb13a8-2755-412d-bdee-65241dd4b28d", "body": "{\n \"network\": {\n \"device_intf\": \"enx207bd2620617\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"INFO\",\n \"startup_timeout\": 60,\n \"monitor_period\": 60\n}", "latency": 0, "statusCode": 200, @@ -772,14 +772,14 @@ "responseMode": null }, { - "uuid": "12928dee-d2c8-4730-a1be-26ad796811dd", + "uuid": "04dae1d8-3ff8-49d8-8068-33cfbaf00a4c", "type": "http", "documentation": "Obtain the current Testrun version", "method": "get", "endpoint": "system/version", "responses": [ { - "uuid": "f3afbdaf-e78c-4248-822a-853ea70ff621", + "uuid": "f08daab2-08f0-40bc-8c28-0fffc7c39b91", "body": "{\n \"installed_version\": \"1.0\",\n \"update_available\": true,\n \"latest_version\": \"1.1\",\n \"latest_version_url\": \"https://github.com/google/testrun/releases/tag/v1.1\"\n}", "latency": 0, "statusCode": 200, @@ -798,7 +798,7 @@ "callbacks": [] }, { - "uuid": "e37c0fbc-13c6-4c91-bbee-8eea148dd8d5", + "uuid": "8e9b2909-40ff-41c1-a013-4091f70f8a8b", "body": "{\n \"installed_version\": \"1.1\",\n \"installed_release_date\": \"20 Oct 2023\",\n \"update_available\": false,\n \"latest_version\": \"1.1\",\n \"latest_version_url\": \"https://github.com/google/testrun/releases/tag/v1.1\"\n}", "latency": 0, "statusCode": 200, @@ -820,14 +820,14 @@ "responseMode": null }, { - "uuid": "ac0ea995-ea90-4c6a-8767-3104e149de68", + "uuid": "eadeb46e-c9d5-4f97-9a8d-ed12ee53c789", "type": "http", "documentation": "Delete a Testrun report", "method": "delete", - "endpoint": "report", + "endpoint": "report/{report_name}", "responses": [ { - "uuid": "35b1585b-13d0-4702-9733-9e2f7fc08ab9", + "uuid": "5654e8d1-9a50-4bd5-bd0d-1ea48edfe825", "body": "{\n \"success\": \"Successfully deleted that report\"\n}", "latency": 0, "statusCode": 200, @@ -846,7 +846,7 @@ "callbacks": [] }, { - "uuid": "ac611b0e-8171-4981-a7e8-93af0a065f8a", + "uuid": "e5ff5f3c-1a00-42a7-959b-25b5196f6777", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -865,7 +865,7 @@ "callbacks": [] }, { - "uuid": "7589fcf9-43c8-4ab8-8abf-a64ea115b038", + "uuid": "1b125433-2947-46b9-a608-71ad99f73dbb", "body": "{\n \"error\": \"That Testrun report could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -884,7 +884,7 @@ "callbacks": [] }, { - "uuid": "220e4ba9-6463-4dc3-b714-f77643706b7d", + "uuid": "15c46ab4-a9b9-43ba-b9bb-f8f517a3862c", "body": "{\n \"error\": \"An error occurred whilst deleting the report\"\n}", "latency": 0, "statusCode": 500, @@ -906,14 +906,14 @@ "responseMode": null }, { - "uuid": "f1d72c42-6f48-488d-a394-4d31b9a1da5e", + "uuid": "40b2c138-af73-419a-af00-37efe9e08dfc", "type": "http", "documentation": "Get a Testrun PDF report", "method": "get", - "endpoint": "report/{device_name}/{timestamp}", + "endpoint": "report/{folder_name}", "responses": [ { - "uuid": "09092f2d-907b-452c-b47b-b2f3b9b47189", + "uuid": "96a9dd85-3b4d-458d-97ff-974e62f924b4", "body": "", "latency": 0, "statusCode": 200, @@ -932,7 +932,7 @@ "callbacks": [] }, { - "uuid": "ee16a180-9d32-4784-9574-701cf56ff370", + "uuid": "af75f83b-13b5-488a-8784-b60c80e1ea82", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -951,7 +951,7 @@ "callbacks": [] }, { - "uuid": "e0464663-9ef1-49d4-941c-926aa06b0907", + "uuid": "fe05445c-6603-4f08-8c62-c41501162f19", "body": "{\n \"error\": \"That Testrun report could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -970,7 +970,7 @@ "callbacks": [] }, { - "uuid": "a7fbb2c8-81dc-4a40-80d6-482119314086", + "uuid": "024ee666-bc35-491c-b5f1-3cd7282ac857", "body": "{\n \"error\": \"An error occurred whilst getting the report\"\n}", "latency": 0, "statusCode": 500, @@ -992,14 +992,14 @@ "responseMode": null }, { - "uuid": "239b1309-52da-4dc8-b914-35c1be6d2ff9", + "uuid": "ce48617f-b7d2-4284-925d-964ac4e453f2", "type": "http", "documentation": "Save a device configuration", "method": "post", "endpoint": "device/edit", "responses": [ { - "uuid": "71e21fad-c93c-4efa-9929-c1496c44ecba", + "uuid": "91baca23-d100-40a4-9a53-b0c3b4cb1cb0", "body": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": false\n },\n \"nmap\": {\n \"enabled\": true\n },\n \"dhcp\": {\n \"enabled\": false\n }\n }\n}", "latency": 0, "statusCode": 200, @@ -1026,7 +1026,7 @@ "callbacks": [] }, { - "uuid": "4ec87c14-fff3-4273-ae07-70ea45670174", + "uuid": "bdf23ad3-bd3c-4b55-b412-19d0c124f7f7", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -1045,7 +1045,7 @@ "callbacks": [] }, { - "uuid": "329afa70-8f92-4b15-9280-be1336e5e32a", + "uuid": "4cd37a90-8380-4132-bfd1-30af6eccaa8b", "body": "{\n \"error\": \"A device with that MAC address could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -1064,7 +1064,7 @@ "callbacks": [] }, { - "uuid": "1456f02a-ec93-47d5-8dfe-7467e325ba6e", + "uuid": "3b65d00d-215c-4d0b-9336-74f5b63444d7", "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}", "latency": 0, "statusCode": 409, @@ -1086,14 +1086,14 @@ "responseMode": null }, { - "uuid": "34d39075-6c44-41c9-9221-a9ed7ae27d3e", + "uuid": "a760ed7c-5ad0-43ae-96aa-cf9357a157a3", "type": "http", "documentation": "Get a Testrun results archive", "method": "get", "endpoint": "export/{device_name}/{timestamp}", "responses": [ { - "uuid": "73d2266f-2bdd-4128-ba82-bb62a454c335", + "uuid": "2bc7a814-994e-44cb-b141-1b63ee131332", "body": "", "latency": 0, "statusCode": 200, @@ -1112,7 +1112,7 @@ "callbacks": [] }, { - "uuid": "63865c53-a848-47cf-804d-a6ca606249f5", + "uuid": "ff784b09-cb7b-4aa4-a6c4-767d35349dff", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -1131,7 +1131,7 @@ "callbacks": [] }, { - "uuid": "52b96932-a1c4-4e7d-b29e-af08922bbcea", + "uuid": "80ec0e66-4f0f-485d-9a17-5f0d69543da8", "body": "{\n \"error\": \"Test results could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -1150,7 +1150,7 @@ "callbacks": [] }, { - "uuid": "3bcb2d6d-3290-43bb-8392-7bfffda4feae", + "uuid": "4198d1fd-0caf-46cd-a803-2d7725adf5e0", "body": "{\n \"error\": \"An error occurred whilst getting the test attempt\"\n}", "latency": 0, "statusCode": 500, @@ -1172,14 +1172,14 @@ "responseMode": null }, { - "uuid": "0aed9026-18f5-4055-9670-83007ea18aaa", + "uuid": "a9101349-5805-4914-ad13-06a5b466cfd1", "type": "http", "documentation": "Shutdown Testrun", "method": "post", "endpoint": "system/shutdown", "responses": [ { - "uuid": "3fc98bfb-f662-4ef4-8fa1-da522ea18993", + "uuid": "16d53ce3-5e2c-49c6-8ecd-0b519ebcd8c0", "body": "null", "latency": 0, "statusCode": 200, @@ -1198,7 +1198,7 @@ "callbacks": [] }, { - "uuid": "6ac393e4-9fbd-466a-804c-cdce26b0bc01", + "uuid": "e89cfa88-6eed-4a18-a8c0-19a90f6ad1d4", "body": "{\n \"error\": \"Unable to shutdown. A test is currently in progress.\"\n}", "latency": 0, "statusCode": 400, @@ -1220,15 +1220,15 @@ "responseMode": null }, { - "uuid": "6d6b9fa9-c961-4007-89f3-9ea9cccc05e7", + "uuid": "36128197-31dc-4b55-bcbe-f42c958d0e1b", "type": "http", "documentation": "List root CA certificates", "method": "get", "endpoint": "system/config/certs", "responses": [ { - "uuid": "581b126d-fadd-4461-bece-97b5b1fd97fd", - "body": "[\n {\n \"name\": \"iot.bms.google.com\",\n \"organisation\": \"Google, Inc.\",\n \"expires\": \"2024-09-01T09:00:12Z\",\n \"status\": \"Valid\"\n },\n {\n \"name\": \"sensor.bms.google.com\",\n \"organisation\": \"Google, Inc.\",\n \"expires\": \"2022-09-01T09:00:12Z\",\n \"status\": \"Expired\"\n }\n]", + "uuid": "02f303c1-8125-4411-9e6f-bea53142ac90", + "body": "[\n {\n \"name\": \"iot.bms.google.com\",\n \"organisation\": \"Google, Inc.\",\n \"expires\": \"2024-09-01T09:00:12Z\",\n \"status\": \"Valid\",\n \"type\": \"root\"\n },\n {\n \"name\": \"sensor.bms.google.com\",\n \"organisation\": \"Google, Inc.\",\n \"expires\": \"2022-09-01T09:00:12Z\",\n \"status\": \"Expired\",\n \"type\": \"intermediate\"\n }\n]", "latency": 0, "statusCode": 200, "label": "Listing 2x certificates", @@ -1246,7 +1246,7 @@ "callbacks": [] }, { - "uuid": "b9fa58e6-95c8-45ea-b6fb-a16287a6e65b", + "uuid": "e0c72cbb-bbc3-43c8-a673-fd55399cef2f", "body": "[]", "latency": 0, "statusCode": 200, @@ -1268,14 +1268,14 @@ "responseMode": null }, { - "uuid": "fb33213d-4448-431c-8733-1ce292644af6", + "uuid": "c1da7f9f-6f12-48fa-8c4c-df30664ca393", "type": "http", "documentation": "", "method": "delete", "endpoint": "system/config/certs", "responses": [ { - "uuid": "63d0656a-2dc4-4a8e-bf53-8de209485c7f", + "uuid": "d4368a95-3569-4901-b6d7-d0302e51bb57", "body": "{\n \"success\": \"Successfully deleted that certificate\"\n}", "latency": 0, "statusCode": 200, @@ -1294,7 +1294,7 @@ "callbacks": [] }, { - "uuid": "906f342a-0981-43a7-a0a2-7fd862cc0ac9", + "uuid": "d0d889e9-dfb3-4cb8-bf4b-638e131604de", "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -1316,14 +1316,14 @@ "responseMode": null }, { - "uuid": "0e927c03-7a2f-41cb-8404-bd94e9213fd3", + "uuid": "03fc347f-20b4-461d-a865-a6e56917166c", "type": "http", "documentation": "Upload root CA certificate", "method": "post", "endpoint": "system/config/certs", "responses": [ { - "uuid": "37e4a9bf-7b8b-42df-a62e-adb27faf7c1b", + "uuid": "c99bcffd-5a14-4d78-9fb4-1cc7ef4b3d2f", "body": "{\n \"success\": \"Successfully uploaded that certificate to the store\"\n}", "latency": 0, "statusCode": 200, @@ -1342,7 +1342,7 @@ "callbacks": [] }, { - "uuid": "5d5640b8-2b53-4101-8e31-e056cd3771dc", + "uuid": "5a962965-fd44-4129-b5a7-a3be9fac7b8d", "body": "{\n \"error\": \"Failed to upload certificate. Is it in the correct format?\"\n}", "latency": 0, "statusCode": 400, @@ -1361,7 +1361,7 @@ "callbacks": [] }, { - "uuid": "2a46c346-843c-4c85-8d4c-a55bc6387b34", + "uuid": "6899cabd-3396-4a29-9599-61fde524f74d", "body": "{\n \"error\": \"That certificate has already been uploaded\"\n}", "latency": 0, "statusCode": 409, @@ -1383,15 +1383,15 @@ "responseMode": null }, { - "uuid": "64b9226c-ae14-4559-883f-470212f63c57", + "uuid": "6325baee-2cd2-4d35-a69c-57cce8235450", "type": "http", "documentation": "List the test modules available", "method": "get", "endpoint": "system/modules", "responses": [ { - "uuid": "88e55282-3b57-476c-9716-b28c5ad795f0", - "body": "[\n \"Connection\",\n \"Services\",\n \"NTP\",\n \"DNS\"\n \"Protocol\",\n \"TLS\"\n]", + "uuid": "32b5cc50-d76b-4036-839b-ca850cf578e1", + "body": "[\n \"Connection\",\n \"Services\",\n \"NTP\",\n \"DNS\",\n \"Protocol\",\n \"TLS\"\n]", "latency": 0, "statusCode": 200, "label": "", @@ -1412,14 +1412,14 @@ "responseMode": null }, { - "uuid": "90a6beb3-a4c0-491f-9beb-b36c29e0dee5", + "uuid": "b6def8f1-0275-4b95-a327-68c75b3c871b", "type": "http", "documentation": "Fetch a list of the profiles saved by the user", "method": "get", "endpoint": "profiles", "responses": [ { - "uuid": "26526b9e-8288-473d-826c-275511c789ec", + "uuid": "fef3e6dc-6bbf-48f8-b035-c4ede094f52d", "body": "[\n {\n \"name\": \"Primary profile\",\n \"status\": \"Valid\",\n \"created\": \"2024-05-23 12:38:26\",\n \"version\": \"v1.3\",\n \"questions\": [\n [\n {\n \"question\": \"What type of device is this?\",\n \"type\": \"select\",\n \"options\": [\n \"IoT Sensor\",\n \"IoT Controller\",\n \"Smart Device\",\n \"Something else\"\n ],\n \"answer\": \"IoT Sensor\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"type\": \"text-long\",\n \"answer\": \"Installed in a building\",\n \"validation\": {\n \"max\": \"128\",\n \"required\": true\n }\n },\n {\n \"question\": \"What is the email of the device owner(s)?\",\n \"type\": \"email-multiple\",\n \"answer\": \"boddey@google.com, cmeredith@google.com\",\n \"validation\": {\n \"required\": true,\n \"max\": \"128\"\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"answer\": \"Google\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"answer\": \"Yes\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"description\": \"This tells us about the data your device will collect\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 2\n ],\n \"options\": [\n \"The device collects any Personal Identifiable Information (PII) or Personal Health Information (PHI)\",\n \"The device collects intellectual property and trade secrets, sensitive business data, critical infrastructure data, identity assets\",\n \"The device stream confidential business data in real-time (seconds)?\"\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 1,\n 5\n ],\n \"options\": [\n \"PII/PHI, confidential business data, or crown jewel data is transmitted to a destination outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device encrypts data during transmission\",\n \"The device network protocol is well-established and currently used by Google\"\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"answer\": \"Yes\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\"\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 2,\n 3\n ],\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\"\n ]\n }\n ]\n ]\n }\n]", "latency": 0, "statusCode": 200, @@ -1433,12 +1433,12 @@ "rulesOperator": "OR", "disableTemplating": false, "fallbackTo404": false, - "default": true, + "default": false, "crudKey": "id", "callbacks": [] }, { - "uuid": "6234def9-6b47-4ed8-a416-d160b25a99c8", + "uuid": "ad3e476f-3233-45dd-8d39-9e165374ab44", "body": "[]", "latency": 0, "statusCode": 200, @@ -1455,19 +1455,38 @@ "default": false, "crudKey": "id", "callbacks": [] + }, + { + "uuid": "9b2c9728-44e8-4000-ba3f-ba849e5b9d71", + "body": "[\n {\n \"name\": \"Primary profile\",\n \"status\": \"Valid\",\n \"created\": \"2024-05-23 12:38:26\",\n \"version\": \"v1.3\",\n \"questions\": [\n [\n {\n \"question\": \"What type of device is this?\",\n \"type\": \"select\",\n \"options\": [\n \"IoT Sensor\",\n \"IoT Controller\",\n \"Smart Device\",\n \"Something else\"\n ],\n \"answer\": \"IoT Sensor\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"type\": \"text-long\",\n \"answer\": \"Installed in a building\",\n \"validation\": {\n \"max\": \"128\",\n \"required\": true\n }\n },\n {\n \"question\": \"What is the email of the device owner(s)?\",\n \"type\": \"email-multiple\",\n \"answer\": \"boddey@google.com, cmeredith@google.com\",\n \"validation\": {\n \"required\": true,\n \"max\": \"128\"\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"answer\": \"Google\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"answer\": \"Yes\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"description\": \"This tells us about the data your device will collect\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 2\n ],\n \"options\": [\n \"The device collects any Personal Identifiable Information (PII) or Personal Health Information (PHI)\",\n \"The device collects intellectual property and trade secrets, sensitive business data, critical infrastructure data, identity assets\",\n \"The device stream confidential business data in real-time (seconds)?\"\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 1,\n 5\n ],\n \"options\": [\n \"PII/PHI, confidential business data, or crown jewel data is transmitted to a destination outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device encrypts data during transmission\",\n \"The device network protocol is well-established and currently used by Google\"\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"answer\": \"Yes\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\"\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"answer\": [\n 2,\n 3\n ],\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\"\n ]\n }\n ]\n ]\n },\n {\n \"name\": \"Draft profile old version\",\n \"version\": \"2.4.0-beta.2\",\n \"created\": \"2026-05-28T06:47:56.958894\",\n \"status\": \"Draft\",\n \"risk\": null,\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"test\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"default\": \"N/A\",\n \"answer\": \"N/A\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"No\",\n \"risk\": \"High\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n },\n {\n \"name\": \"Expired profile\",\n \"version\": \"2.3.0-beta.2\",\n \"created\": \"2024-05-28T06:47:56.958894\",\n \"status\": \"Expired\",\n \"risk\": null,\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Expired\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Third Party\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"default\": \"N/A\",\n \"answer\": \"N/A\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 1\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"No\",\n \"risk\": \"High\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 1\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 1\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"Expired\"\n }\n ]\n }\n]", + "latency": 0, + "statusCode": 200, + "label": "Several profiles", + "headers": [], + "bodyType": "INLINE", + "filePath": "", + "databucketID": "", + "sendFileAsBody": false, + "rules": [], + "rulesOperator": "OR", + "disableTemplating": false, + "fallbackTo404": false, + "default": true, + "crudKey": "id", + "callbacks": [] } ], "responseMode": null }, { - "uuid": "86267532-5d37-4c12-9dfd-81d16c6164f1", + "uuid": "94e2892b-2650-4508-ac19-63c0529b5f65", "type": "http", "documentation": "Delete a profile", "method": "delete", "endpoint": "profiles", "responses": [ { - "uuid": "0c66bb41-e28b-40fe-866d-1a0c140489ec", + "uuid": "580bbf99-f7a2-4dcf-8e75-d487e2097e34", "body": "{\n \"success\": \"Successfully deleted the risk profile\"\n}", "latency": 0, "statusCode": 200, @@ -1486,7 +1505,7 @@ "callbacks": [] }, { - "uuid": "b9c4540e-d5cf-4ee3-a450-68c16552355a", + "uuid": "f03fc543-de7c-43ff-88a0-535ebceeac48", "body": "{\n \"error\": \"A profile with that name could not be found\"\n}", "latency": 0, "statusCode": 404, @@ -1508,14 +1527,14 @@ "responseMode": null }, { - "uuid": "45635915-a144-46a9-b59a-f4b9d2fc8ebe", + "uuid": "727b4183-2d2c-4c0f-9102-77bd53a9c9f1", "type": "http", "documentation": "Create or update a risk profile", "method": "post", "endpoint": "profiles", "responses": [ { - "uuid": "5c6e3b25-a4cc-48e8-b04f-bc7f9bcf0719", + "uuid": "23def1be-83c7-43d8-8054-14979e2358bb", "body": "{\n \"success\": \"Successfully created a risk profile\"\n}", "latency": 0, "statusCode": 201, @@ -1534,7 +1553,7 @@ "callbacks": [] }, { - "uuid": "f65866fa-3f7c-4cd0-b731-a2cbbfbdea10", + "uuid": "c7ea7dd1-aa39-4d5d-9e64-6b88428aca59", "body": "{\n \"error\": \"Invalid request received\"\n}", "latency": 0, "statusCode": 400, @@ -1553,7 +1572,7 @@ "callbacks": [] }, { - "uuid": "db611e13-2076-4af0-855d-fc2d7447e395", + "uuid": "d0694f65-67da-4f86-90b9-54e94dc78a0b", "body": "{\n \"error\": \"A profile with that name already exists.\"\n}", "latency": 0, "statusCode": 404, @@ -1575,15 +1594,15 @@ "responseMode": null }, { - "uuid": "26f0f76f-e787-4ebe-a3f8-ea3a6004bc15", + "uuid": "6667bead-2851-46fa-8dee-de8adfc63b25", "type": "http", "documentation": "Fetch the current format of the profiles questionnaire", "method": "get", "endpoint": "profiles/format", "responses": [ { - "uuid": "a044a39d-e40d-48b1-83f4-762adcc1c444", - "body": "[\n {\n \"question\": \"What type of device is this?\",\n \"type\": \"select\",\n \"options\": [\n \"IoT Sensor\",\n \"IoT Controller\",\n \"Smart Device\",\n \"Something else\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"128\",\n \"required\": true\n }\n },\n {\n \"question\": \"What is the email of the device owner(s)?\",\n \"type\": \"email-multiple\",\n \"validation\": {\n \"required\": true,\n \"max\": \"128\"\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"description\": \"This tells us about the data your device will collect\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device collects any Personal Identifiable Information (PII) or Personal Health Information (PHI)\",\n \"The device collects intellectual property and trade secrets, sensitive business data, critical infrastructure data, identity assets\",\n \"The device stream confidential business data in real-time (seconds)?\"\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential business data, or crown jewel data is transmitted to a destination outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device encrypts data during transmission\",\n \"The device network protocol is well-established and currently used by Google\"\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\"\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\"\n ]\n }\n]", + "uuid": "c1e2ec8b-e1ab-4b8f-b6db-1559fea8da5d", + "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Describe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": { \"max\": \"512\", \"required\": true }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Google\", \"risk\": \"Limited\" },\n { \"text\": \"Third Party\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes\" },\n { \"text\": \"No\" },\n { \"text\": \"N/A\" }\n ],\n \"default\": \"N/A\",\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"Types of data transmitted and technical transmission methods.\",\n \"type\": \"select-multiple\",\n \"options\": [\n { \"text\": \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\", \"risk\": \"High\" },\n { \"text\": \"Data transmission occurs across less-trusted networks (e.g. the internet).\", \"risk\": \"High\" },\n { \"text\": \"A failure in data transmission would likely have a substantial negative impact\", \"risk\": \"High\" },\n { \"text\": \"A confidentiality breach during transmission would have a substantial negative impact\", \"risk\": \"High\" },\n { \"text\": \"The device does not encrypt data during transmission\", \"risk\": \"High\" },\n { \"text\": \"None of the above\", \"risk\": \"Limited\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes\", \"risk\": \"Limited\" },\n { \"text\": \"No\", \"risk\": \"High\" },\n { \"text\": \"I don't know\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"Remote management and access characteristics.\",\n \"type\": \"select-multiple\",\n \"options\": [\n { \"text\": \"PII/PHI, or confidential business data is accessible from the device without authentication\", \"risk\": \"High\" },\n { \"text\": \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\", \"risk\": \"High\" },\n { \"text\": \"Authentication is not required for remote access\", \"risk\": \"High\" },\n { \"text\": \"The management interface is accessible from the public internet\", \"risk\": \"High\" },\n { \"text\": \"Static credentials are used for administration\", \"risk\": \"High\" },\n { \"text\": \"None of the above\", \"risk\": \"Limited\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"Context of the device within larger systems and processes.\",\n \"type\": \"select-multiple\",\n \"options\": [\n { \"text\": \"The device monitors an environment for active risks to human life.\", \"risk\": \"High\" },\n { \"text\": \"The device is used to convey people, or critical property.\", \"risk\": \"High\" },\n { \"text\": \"The device controls robotics in human-accessible spaces.\", \"risk\": \"High\" },\n { \"text\": \"The device controls physical access systems.\", \"risk\": \"High\" },\n { \"text\": \"The device is involved in processes required by regulations, or compliance.\", \"risk\": \"High\" },\n { \"text\": \"The device's failure would cause faults in other high-criticality processes.\", \"risk\": \"High\" },\n { \"text\": \"None of the above\", \"risk\": \"Limited\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Wireless Security\",\n \"question\": \"What types of wireless connectivity does this device support or utilize? (Select all that apply)\",\n \"description\": \"Select all active or physically present wireless interfaces.\",\n \"type\": \"select-multiple\",\n \"options\": [\n {\n \"text\": \"Cellular / WWAN (e.g., LTE, 5G, NB-IoT, eSIM)\",\n \"risk\": \"High\"\n },\n {\n \"text\": \"LPWAN / Long-Range RF (e.g., LoRaWAN)\",\n \"risk\": \"High\"\n },\n {\n \"text\": \"Wi-Fi / WLAN - broadcasts its own network (acts as an Access Point / Wi-Fi Direct)\",\n \"risk\": \"High\"\n },\n {\n \"text\": \"Wi-Fi / WLAN - connects as a standard client to an existing network\",\n \"risk\": \"Limited\"\n },\n {\n \"text\": \"Short-range RF (e.g., Bluetooth, BLE, Zigbee)\",\n \"risk\": \"Limited\"\n },\n {\n \"text\": \"None (Hardwired Ethernet or serial connections only)\",\n \"risk\": \"Limited\"\n }\n ],\n \"validation\": {\n \"required\": true\n }\n\n },\n {\n \"category\": \"Physical Security\",\n \"question\": \"Are physical debug interfaces (JTAG, UART, SWD) disabled or physically inaccessible?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes, disabled in hardware/firmware\", \"risk\": \"Limited\" },\n { \"text\": \"No, ports are active and accessible\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Authentication\",\n \"question\": \"Does the device support integration with Google's SSO or MFA for administrative access?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes, supports Google’s SSO or MFA\", \"risk\": \"Limited\" },\n { \"text\": \"No, uses local unique or shared passwords\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Software Integrity\",\n \"question\": \"Is firmware cryptographically signed and verified during the boot process?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes, verified Secure Boot\", \"risk\": \"Limited\" },\n { \"text\": \"No signing used\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Vulnerability Management\",\n \"question\": \"How frequently are security patches released and applied to this device?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Automatically within 30 days of release\", \"risk\": \"Limited\" },\n { \"text\": \"Rarely or no patch support\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Privacy\",\n \"question\": \"Does the device include audio or video recording capabilities?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"No\", \"risk\": \"Limited\" },\n { \"text\": \"Yes\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"category\": \"Logging\",\n \"question\": \"Does the device generate security audit logs (e.g., login attempts, config changes)?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes\", \"risk\": \"Limited\" },\n { \"text\": \"No\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"question\": \"Does the device undergo regular third-party penetration testing?\",\n \"type\": \"select\",\n \"options\": [\n { \"text\": \"Yes\", \"risk\": \"Limited\" },\n { \"text\": \"No\", \"risk\": \"High\" }\n ],\n \"validation\": { \"required\": true }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": { \"max\": \"512\" }\n }\n]\n", "latency": 0, "statusCode": 200, "label": "", @@ -1604,20 +1623,20 @@ "responseMode": null }, { - "uuid": "af7fdcb0-721d-4198-a8ef-c6d8c4eba8c8", + "uuid": "65398138-9bd5-4701-af27-48cd268c30cb", "type": "http", - "documentation": "Get a Testrun PDF profile", - "method": "post", - "endpoint": "report/{profile_name}", + "documentation": "Format for qualification form", + "method": "get", + "endpoint": "devices/format", "responses": [ { - "uuid": "9a759f46-4bc4-433a-be86-e456f069c217", - "body": "", + "uuid": "b23d0607-6f60-47eb-ace7-419759fc772c", + "body": "[\n {\n \"id\": 1,\n \"question\": \"What type of device is this?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Building Automation Gateway\",\n \"risk\": \"High\",\n \"id\": 1\n },\n {\n \"text\": \"IoT Gateway\",\n \"risk\": \"High\",\n \"id\": 2\n },\n {\n \"text\": \"Controller - AHU\",\n \"risk\": \"High\",\n \"id\": 3\n },\n {\n \"text\": \"Controller - Boiler\",\n \"risk\": \"High\",\n \"id\": 4\n },\n {\n \"text\": \"Controller - Chiller\",\n \"risk\": \"High\",\n \"id\": 5\n },\n {\n \"text\": \"Controller - FCU\",\n \"risk\": \"Limited\",\n \"id\": 6\n },\n {\n \"text\": \"Controller - Pump\",\n \"risk\": \"Limited\",\n \"id\": 7\n },\n {\n \"text\": \"Controller - CRAC\",\n \"risk\": \"High\",\n \"id\": 8\n },\n {\n \"text\": \"Controller - VAV\",\n \"risk\": \"Limited\",\n \"id\": 9\n },\n {\n \"text\": \"Controller - VRF\",\n \"risk\": \"Limited\",\n \"id\": 10\n },\n {\n \"text\": \"Controller - Multiple\",\n \"risk\": \"High\",\n \"id\": 11\n },\n {\n \"text\": \"Controller - Other\",\n \"risk\": \"High\",\n \"id\": 12\n },\n {\n \"text\": \"Controller - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 13\n },\n {\n \"text\": \"Controller - Blinds/Facades\",\n \"risk\": \"High\",\n \"id\": 14\n },\n {\n \"text\": \"Controller - Lifts/Elevators\",\n \"risk\": \"High\",\n \"id\": 15\n },\n {\n \"text\": \"Controller - UPS\",\n \"risk\": \"High\",\n \"id\": 16\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 17\n },\n {\n \"text\": \"Sensor - Vibration\",\n \"risk\": \"Limited\",\n \"id\": 18\n },\n {\n \"text\": \"Sensor - Humidity\",\n \"risk\": \"Limited\",\n \"id\": 19\n },\n {\n \"text\": \"Sensor - Water\",\n \"risk\": \"Limited\",\n \"id\": 20\n },\n {\n \"text\": \"Sensor - Occupancy\",\n \"risk\": \"High\",\n \"id\": 21\n },\n {\n \"text\": \"Sensor - Volume\",\n \"risk\": \"Limited\",\n \"id\": 22\n },\n {\n \"text\": \"Sensor - Weight\",\n \"risk\": \"Limited\",\n \"id\": 23\n },\n {\n \"text\": \"Sensor - Weather\",\n \"risk\": \"Limited\",\n \"id\": 24\n },\n {\n \"text\": \"Sensor - Steam\",\n \"risk\": \"High\",\n \"id\": 25\n },\n {\n \"text\": \"Sensor - Air Flow\",\n \"risk\": \"Limited\",\n \"id\": 26\n },\n {\n \"text\": \"Sensor - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 27\n },\n {\n \"text\": \"Sensor - Other\",\n \"risk\": \"High\",\n \"id\": 28\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 29\n },\n {\n \"text\": \"Monitoring - Fire System\",\n \"risk\": \"Limited\",\n \"id\": 30\n },\n {\n \"text\": \"Monitoring - Emergency Lighting\",\n \"risk\": \"Limited\",\n \"id\": 31\n },\n {\n \"text\": \"Monitoring - Other\",\n \"risk\": \"High\",\n \"id\": 32\n },\n {\n \"text\": \"Monitoring - UPS\",\n \"risk\": \"Limited\",\n \"id\": 33\n },\n {\n \"text\": \"Meter - Water\",\n \"risk\": \"Limited\",\n \"id\": 34\n },\n {\n \"text\": \"Meter - Gas\",\n \"risk\": \"Limited\",\n \"id\": 35\n },\n {\n \"text\": \"Meter - Electricity\",\n \"risk\": \"Limited\",\n \"id\": 36\n },\n {\n \"text\": \"Meter - Other\",\n \"risk\": \"High\",\n \"id\": 37\n },\n {\n \"text\": \"Other\",\n \"risk\": \"High\",\n \"id\": 38\n },\n {\n \"text\": \"Data - Storage\",\n \"risk\": \"High\",\n \"id\": 39\n },\n {\n \"text\": \"Data - Processing\",\n \"risk\": \"High\",\n \"id\": 40\n },\n {\n \"text\": \"Tablet\",\n \"risk\": \"High\",\n \"id\": 41\n }\n ]\n },\n {\n \"id\": 2,\n \"question\": \"Please select the technology this device falls into\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Hardware - Access Control\"\n },\n {\n \"id\": 2,\n \"text\": \"Hardware - Air quality\"\n },\n {\n \"id\": 3,\n \"text\": \"Hardware - Asset location tracking\"\n },\n {\n \"id\": 4,\n \"text\": \"Hardware - Audio Visual\"\n },\n {\n \"id\": 5,\n \"text\": \"Hardware - Blinds/Facade\"\n },\n {\n \"id\": 6,\n \"text\": \"Hardware - Cameras\"\n },\n {\n \"id\": 7,\n \"text\": \"Hardware - Catering\"\n },\n {\n \"id\": 8,\n \"text\": \"Hardware - Data Ingestion/Managment\"\n },\n {\n \"id\": 9,\n \"text\": \"Hardware - EV Charging\"\n },\n {\n \"id\": 10,\n \"text\": \"Hardware - Fitness\"\n },\n {\n \"id\": 11,\n \"text\": \"Hardware - HVAC\"\n },\n {\n \"id\": 12,\n \"text\": \"Hardware - Irrigation\"\n },\n {\n \"id\": 13,\n \"text\": \"Hardware - Leak Detection\"\n },\n {\n \"id\": 14,\n \"text\": \"Hardware - Lifts/Elevators\"\n },\n {\n \"id\": 15,\n \"text\": \"Hardware - Lighting\"\n },\n {\n \"id\": 16,\n \"text\": \"Hardware - Metering\"\n },\n {\n \"id\": 17,\n \"text\": \"Hardware - Monitoring\"\n },\n {\n \"id\": 18,\n \"text\": \"Hardware - Occupancy\"\n },\n {\n \"id\": 19,\n \"text\": \"Hardware - System Integration\"\n },\n {\n \"id\": 20,\n \"text\": \"Hardware - Time Management\"\n },\n {\n \"id\": 21,\n \"text\": \"Hardware - UPS\"\n },\n {\n \"id\": 22,\n \"text\": \"Hardware - Waste Management\"\n },\n {\n \"id\": 23,\n \"text\": \"Building Automation\"\n },\n {\n \"id\": 24,\n \"text\": \"Other\"\n }\n ]\n },\n {\n \"id\": 3,\n \"question\": \"Does your device process any sensitive information? \",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Yes\",\n \"risk\": \"High\"\n },\n {\n \"id\": 2,\n \"text\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"id\": 3,\n \"text\": \"I don't know\",\n \"risk\": \"High\"\n }\n ]\n },\n {\n \"id\": 4,\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 5,\n \"question\": \"Is there a second IP port on the device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 6,\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n },\n {\n \"text\": \"N/A\",\n \"id\": 3\n }\n ]\n },\n {\n \"id\": 7,\n \"question\": \"Does the device UI web dashboard have a mechanism to enforce the change of default passwords, SSH, Hardcoded Accounts Credentials and factory-default API keys upon first use?\",\n \"validation\": { \"required\": true },\n \"type\": \"select\",\n \"options\": [\n { \"id\": 1, \"text\": \"Yes, mandatory change required\", \"risk\": \"Limited\" },\n { \"id\": 2, \"text\": \"Yes, but can be bypassed\", \"risk\": \"High\" },\n { \"id\": 3, \"text\": \"No, default credentials remain\", \"risk\": \"High\" },\n { \"id\": 4, \"text\": \"N/A (No password-based login)\", \"risk\": \"High\" }\n ]\n },\n {\n \"id\": 8,\n \"question\": \"Does the device support encrypted management protocols (e.g., HTTPS, SSHv2, TLS 1.2, or TLS 1.3)?\",\n \"validation\": { \"required\": true },\n \"type\": \"select\",\n \"options\": [\n { \"id\": 1, \"text\": \"Yes, only encrypted protocols\", \"risk\": \"Limited\" },\n { \"id\": 2, \"text\": \"Yes, but unencrypted protocols (HTTP/Telnet) are also available\", \"risk\": \"High\" },\n { \"id\": 3, \"text\": \"No, only unencrypted protocols are supported\", \"risk\": \"High\" }\n ]\n },\n {\n \"id\": 9,\n \"question\": \"How are firmware updates delivered and verified on the device?\",\n \"validation\": { \"required\": true },\n \"type\": \"select\",\n \"options\": [\n { \"id\": 1, \"text\": \"Automatic updates with cryptographic signature verification\", \"risk\": \"Limited\" },\n { \"id\": 2, \"text\": \"Manual updates with cryptographic signature verification\", \"risk\": \"Limited\" },\n { \"id\": 3, \"text\": \"Manual updates without signature verification\", \"risk\": \"High\" },\n { \"id\": 4, \"text\": \"The device does not support firmware updates\", \"risk\": \"High\" }\n ]\n },\n {\n \"id\": 10,\n \"question\": \"Does the device include physical tamper-resistant features?\",\n \"validation\": { \"required\": true },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Yes, includes physical seals or chassis intrusion detection\",\n \"risk\": \"Limited\"\n },\n {\n \"id\": 2,\n \"text\": \"No physical tamper protection\",\n \"risk\": \"High\"\n }\n ]\n }\n]", "latency": 0, "statusCode": 200, - "label": "Profile found - no device selected", + "label": "", "headers": [], - "bodyType": "FILE", + "bodyType": "INLINE", "filePath": "", "databucketID": "", "sendFileAsBody": false, @@ -1630,49 +1649,11 @@ "callbacks": [] }, { - "uuid": "c9a09ae7-3158-4956-93ac-4c8a90dfced8", - "body": "", + "uuid": "51bd0189-a753-458b-bfba-8d1d10a31560", + "body": "[{\"question\":\"How will this device be used at Google?\",\"description\":\"Describe your use case. Add links to user journey diagrams and TDD if available.\",\"type\":\"text-long\",\"validation\":{\"max\":\"512\",\"required\":true}},{\"question\":\"Is this device going to be managed by Google or a third party?\",\"description\":\"A manufacturer or supplier is considered third party in this case\",\"type\":\"select\",\"options\":[\"Google\",\"Third Party\"],\"validation\":{\"required\":true}},{\"question\":\"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\"type\":\"select\",\"options\":[\"Yes\",\"No\",\"N/A\"],\"default\":\"N/A\",\"validation\":{\"required\":true}},{\"category\":\"Data Transmission\",\"question\":\"Which of the following statements are true about this device?\",\"description\":\"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\"type\":\"select-multiple\",\"options\":[\"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\"Data transmission occurs across less-trusted networks (e.g. the internet).\",\"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\"A confidentiality breach during transmission would have a substantial negative impact\",\"The device does not encrypt data during transmission\",\"None of the above\"],\"validation\":{\"required\":true}},{\"category\":\"Data Transmission\",\"question\":\"Does the network protocol assure server-to-client identity verification?\",\"type\":\"select\",\"options\":[\"Yes\",\"No\",\"I don't know\"],\"validation\":{\"required\":true}},{\"category\":\"Remote Operation\",\"question\":\"Click the statements that best describe the characteristics of this device.\",\"description\":\"This tells us about how this device is managed remotely.\",\"type\":\"select-multiple\",\"options\":[\"PII/PHI, or confidential business data is accessible from the device without authentication\",\"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\"Authentication is not required for remote access\",\"The management interface is accessible from the public internet\",\"Static credentials are used for administration\",\"None of the above\"],\"validation\":{\"required\":true}},{\"category\":\"Operating Environment\",\"question\":\"Are any of the following statements true about this device?\",\"description\":\"This informs us about what other systems and processes this device is a part of.\",\"type\":\"select-multiple\",\"options\":[\"The device monitors an environment for active risks to human life.\",\"The device is used to convey people, or critical property.\",\"The device controls robotics in human-accessible spaces.\",\"The device controls physical access systems.\",\"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\"The device's failure would cause faults in other high-criticality processes.\",\"None of the above\"],\"validation\":{\"required\":true}},{\"question\":\"Comments\",\"description\":\"Anything else to share?\",\"type\":\"text-long\",\"validation\":{\"max\":\"512\"}}]", "latency": 0, "statusCode": 200, - "label": "Profile found - device selected ", - "headers": [], - "bodyType": "FILE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [], - "rulesOperator": "OR", - "disableTemplating": false, - "fallbackTo404": false, - "default": false, - "crudKey": "id", - "callbacks": [] - }, - { - "uuid": "5f98471e-15b6-47a4-a68d-e98c3a538b40", - "body": "{\n \"error\": \"Profile could not be found\"\n}", - "latency": 0, - "statusCode": 404, - "label": "Profile not found", - "headers": [], - "bodyType": "INLINE", - "filePath": "", - "databucketID": "", - "sendFileAsBody": false, - "rules": [], - "rulesOperator": "OR", - "disableTemplating": false, - "fallbackTo404": false, - "default": false, - "crudKey": "id", - "callbacks": [] - }, - { - "uuid": "767d9e78-386e-4bf7-bec8-71a005efdce9", - "body": "{\n \"error\": \"A device with that mac address could not be found\"\n}", - "latency": 0, - "statusCode": 404, - "label": "Device not found", + "label": "", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1685,13 +1666,23 @@ "default": false, "crudKey": "id", "callbacks": [] - }, + } + ], + "responseMode": null + }, + { + "uuid": "bd738482-fbfb-4e60-9435-36cc94b1852a", + "type": "http", + "documentation": "", + "method": "delete", + "endpoint": "report/f0d4e2f2f541_2026-02-02T17:24:52", + "responses": [ { - "uuid": "5d76bea0-39c1-45f2-80f1-de6f770cb999", - "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}", + "uuid": "5c3cd9b8-0a90-4192-8373-835a55487159", + "body": "{}", "latency": 0, - "statusCode": 500, - "label": "Error occurred", + "statusCode": 200, + "label": "", "headers": [], "bodyType": "INLINE", "filePath": "", @@ -1701,7 +1692,7 @@ "rulesOperator": "OR", "disableTemplating": false, "fallbackTo404": false, - "default": false, + "default": true, "crudKey": "id", "callbacks": [] } @@ -1712,103 +1703,107 @@ "rootChildren": [ { "type": "route", - "uuid": "609cbb2f-b064-4d94-9d47-705f902af57d" + "uuid": "1fe86785-8d24-4abd-b83e-a26022997bcc" + }, + { + "type": "route", + "uuid": "f3b4ed0b-11e2-4d7f-8d08-4b9840c70d5c" }, { "type": "route", - "uuid": "fd6fc843-4a77-4532-ba38-f81cdf345dc2" + "uuid": "ea00c773-fb7c-4889-b140-c2b48cae7819" }, { "type": "route", - "uuid": "32ddc969-bf37-4bd6-a926-329ffa351583" + "uuid": "6299b6ba-2746-4f44-b7ba-2c31bdd795dc" }, { "type": "route", - "uuid": "24a155d7-8d30-458e-bc03-2240ecc95a30" + "uuid": "cf9a795b-6b3d-41cf-8115-3777a3514dc8" }, { "type": "route", - "uuid": "98e836d3-8af1-4321-9cdc-7feba8b40d8e" + "uuid": "ce48617f-b7d2-4284-925d-964ac4e453f2" }, { "type": "route", - "uuid": "239b1309-52da-4dc8-b914-35c1be6d2ff9" + "uuid": "adeda7ee-f6d2-4ab1-ac2a-861d1f2abaed" }, { "type": "route", - "uuid": "a218761a-6ac6-466b-932e-9d58b6ec3a2e" + "uuid": "1f580148-ed7a-47f4-bc4b-6b3ce5c8d8a2" }, { "type": "route", - "uuid": "dcead7f8-71da-43ef-974b-ceddf8f262cc" + "uuid": "69fe7ba0-1a81-482f-b8a7-f92cb75d957c" }, { "type": "route", - "uuid": "9a9482ee-09d6-4ab7-9fb0-bbfed791bd9d" + "uuid": "9a013389-fa81-4b39-a60b-d0305eab5f58" }, { "type": "route", - "uuid": "217a5e1b-fda6-4930-9f05-1372bc11590c" + "uuid": "a9101349-5805-4914-ad13-06a5b466cfd1" }, { "type": "route", - "uuid": "0aed9026-18f5-4055-9670-83007ea18aaa" + "uuid": "d808737a-e666-4ff4-89d6-7df7da041710" }, { "type": "route", - "uuid": "a8ba2fcb-8e05-42f4-9d5f-cea63aa8ead7" + "uuid": "eadeb46e-c9d5-4f97-9a8d-ed12ee53c789" }, { "type": "route", - "uuid": "ac0ea995-ea90-4c6a-8767-3104e149de68" + "uuid": "04dae1d8-3ff8-49d8-8068-33cfbaf00a4c" }, { "type": "route", - "uuid": "12928dee-d2c8-4730-a1be-26ad796811dd" + "uuid": "40b2c138-af73-419a-af00-37efe9e08dfc" }, { "type": "route", - "uuid": "f1d72c42-6f48-488d-a394-4d31b9a1da5e" + "uuid": "a760ed7c-5ad0-43ae-96aa-cf9357a157a3" }, { "type": "route", - "uuid": "34d39075-6c44-41c9-9221-a9ed7ae27d3e" + "uuid": "03fc347f-20b4-461d-a865-a6e56917166c" }, { "type": "route", - "uuid": "0e927c03-7a2f-41cb-8404-bd94e9213fd3" + "uuid": "36128197-31dc-4b55-bcbe-f42c958d0e1b" }, { "type": "route", - "uuid": "6d6b9fa9-c961-4007-89f3-9ea9cccc05e7" + "uuid": "c1da7f9f-6f12-48fa-8c4c-df30664ca393" }, { "type": "route", - "uuid": "fb33213d-4448-431c-8733-1ce292644af6" + "uuid": "6325baee-2cd2-4d35-a69c-57cce8235450" }, { "type": "route", - "uuid": "64b9226c-ae14-4559-883f-470212f63c57" + "uuid": "b6def8f1-0275-4b95-a327-68c75b3c871b" }, { "type": "route", - "uuid": "90a6beb3-a4c0-491f-9beb-b36c29e0dee5" + "uuid": "94e2892b-2650-4508-ac19-63c0529b5f65" }, { "type": "route", - "uuid": "86267532-5d37-4c12-9dfd-81d16c6164f1" + "uuid": "727b4183-2d2c-4c0f-9102-77bd53a9c9f1" }, { "type": "route", - "uuid": "45635915-a144-46a9-b59a-f4b9d2fc8ebe" + "uuid": "6667bead-2851-46fa-8dee-de8adfc63b25" }, { "type": "route", - "uuid": "26f0f76f-e787-4ebe-a3f8-ea3a6004bc15" + "uuid": "65398138-9bd5-4701-af27-48cd268c30cb" }, { "type": "route", - "uuid": "af7fdcb0-721d-4198-a8ef-c6d8c4eba8c8" + "uuid": "bd738482-fbfb-4e60-9435-36cc94b1852a" } ], "proxyMode": false, @@ -1846,6 +1841,14 @@ "value": "" } ], - "data": [], + "data": [ + { + "uuid": "1e9c0f04-0af0-4a4d-8c54-a5438b35e08f", + "id": "cntg", + "name": "device_list", + "documentation": "", + "value": "[\n {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"status\": \"Valid\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB 140\",\n \"type\": \"IoT Gateway\",\n \"technology\": \"Hardware - Air quality\",\n \"test_pack\": \"Pilot Assessment\",\n \"firmware\": \"3.14 omega\",\n \"additional_info\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"Please select the technology this device falls into\",\n \"answer\": \"Building Automation\"\n },\n {\n \"question\": \"Does your device process any sensitive information? \",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Is there a second IP port on the device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"answer\": \"No\"\n }\n ],\n \"test_modules\": {\n \"dns\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"nmap\": {\n \"enabled\": false\n }\n }\n },\n {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"status\": \"Invalid\",\n \"manufacturer\": \"Manufacturer X\",\n \"model\": \"Device X\",\n \"type\": \"IoT Gateway\",\n \"technology\": \"Building Automation\",\n \"test_pack\": \"Device Qualification\",\n \"additional_info\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"Please select the technology this device falls into\",\n \"answer\": \"Building Automation\"\n },\n {\n \"question\": \"Does your device process any sensitive information? \",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Is there a second IP port on the device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"answer\": \"No\"\n }\n ],\n \"test_modules\": {\n \"connection\": {\"enabled\": true},\n \"dns\" : {\"enabled\": true},\n \"ntp\": {\"enabled\": true},\n \"protocol\": {\"enabled\": false},\n \"services\": {\"enabled\": true},\n \"tls\": {\"enabled\": false}\n }\n },\n {\n \"mac_addr\": \"aa:bb:cc:dd:ee:fa\",\n \"status\": \"Valid\",\n \"manufacturer\": \"Manufacturer Y\",\n \"model\": \"Device y\",\n \"type\": \"IoT Gateway\",\n \"technology\": \"Building Automation\",\n \"test_pack\": \"Device Qualification\",\n \"additional_info\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"Please select the technology this device falls into\",\n \"answer\": \"Building Automation\"\n },\n {\n \"question\": \"Does your device process any sensitive information? \",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Is there a second IP port on the device?\",\n \"answer\": \"No\"\n },\n {\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"answer\": \"Yes\"\n }\n ],\n \"test_modules\": {\n \"connection\": {\"enabled\": false},\n \"dns\" : {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"protocol\": {\"enabled\": false},\n \"services\": {\"enabled\": false},\n \"tls\": {\"enabled\": false}\n }\n }\n]" + } + ], "callbacks": [] } \ No newline at end of file diff --git a/docs/dev/postman.json b/docs/dev/postman.json index 08369ac55..54db81229 100644 --- a/docs/dev/postman.json +++ b/docs/dev/postman.json @@ -1,4246 +1,4243 @@ { - "info": { - "_postman_id": "f42dd4c6-e1a3-4ae3-a991-24cceb4d2627", - "name": "Testrun", - "description": "API endpoints for the Testrun API", - "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", - "_exporter_id": "37950585" - }, - "item": [ - { - "name": "Get System Interfaces", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/interfaces", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "interfaces" - ] - }, - "description": "Obtain a list of applicable and available network adapters for use in testing" - }, - "response": [ - { - "name": "Interfaces Available (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/interfaces", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "interfaces" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"enx00e04c020fa8\": \"00:e0:4c:02:0f:a8\",\n \"enx207bd26205e9\": \"20:7b:d2:62:05:e9\"\n}" - }, - { - "name": "No interfaces (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/interfaces", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "interfaces" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{}" - } - ] - }, - { - "name": "Get Configuration", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/config", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config" - ] - }, - "description": "Get the current system configuration" - }, - "response": [ - { - "name": "Get Configuration (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/config", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"network\": {\n \"device_intf\": \"enx207bd2620617\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"INFO\",\n \"startup_timeout\": 60,\n \"monitor_period\": 60,\n \"runtime\": 120\n}" - } - ] - }, - { - "name": "Set Configuration", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"network\": {\n \"device_intf\": \"enx207bd2620617\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"startup_timeout\": 60,\n \"monitor_period\": 20,\n \"runtime\": 120,\n \"org_name\": \"Google\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/config", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config" - ] - }, - "description": "Update the current system configuration" - }, - "response": [ - { - "name": "Configuration Set (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120, // Optional\n \"startup_timeout\": 60, // Optional\n \"monitor_period\": 60 // Optional\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/config", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120,\n \"startup_timeout\": 60,\n \"monitor_period\": 60\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120,\n \"startup_timeout\": 60\n \"monitor_period\": 60 \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/config", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid JSON received\"\n}" - } - ] - }, - { - "name": "Start Testrun", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - }, - "description": "Start Testrun against a target device" - }, - "response": [ - { - "name": "Starting (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" - }, - { - "name": "Invalid Request (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"firmware\": \"1.2.2\"\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff,\n \"firmware\": \"1.2.2\"\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid JSON received\"\n}" - }, - { - "name": "Device Not Found (404)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:99\",\n \"firmware\": \"1.2.2\"\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A device with that MAC address could not be found\"\n}" - }, - { - "name": "Testrun Already in Progress (409)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/start", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "start" - ] - } - }, - "status": "Conflict", - "code": 409, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Testrun cannot be started whilst a test is running on another device\"\n}" - } - ] - }, - { - "name": "Stop Testrun", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/stop", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "stop" - ] - }, - "description": "Stop Testrun from running against a device" - }, - "response": [ - { - "name": "Stopped (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/stop", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "stop" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Testrun stopped\"\n}" - }, - { - "name": "Not Running (404)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/stop", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "stop" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Testrun is not currently running\"\n}" - } - ] - }, - { - "name": "Get System Status", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - }, - "description": "Get the current status of Testrun. Suggested that this is called every 5 seconds to capture most events" - }, - "response": [ - { - "name": "Monitoring (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Monitoring\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" - }, - { - "name": "Test in Progress (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"In Progress\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Non-Compliant\"\n }\n ]\n }\n}" - }, - { - "name": "Cancelled (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Cancelled\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:24:00.123Z\",\n \"report\": null,\n \"tests\": {\n \"total\": 22,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n }\n ]\n }\n}" - }, - { - "name": "Waiting for Device (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" - }, - { - "name": "Non-Compliant (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Non-Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Non-Compliant\",\n \"required_result\": \"Compliant\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Compliant\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}" - }, - { - "name": "Compliant (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}" - }, - { - "name": "Not Running - Idle (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/status", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "status" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"status\": \"Idle\",\n \"device\": null,\n \"started\": null,\n \"finished\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" - } - ] - }, - { - "name": "Shutdown Testrun", - "request": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/system/shutdown", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "shutdown" - ] - }, - "description": "Stop the Testrun framework" - }, - "response": [ - { - "name": "Shutdown Testrun (200)", - "originalRequest": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/system/shutdown", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "shutdown" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "null" - }, - { - "name": "Test in Progress (400)", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/shutdown", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "shutdown" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Unable to shutdown. A test is currently in progress.\"\n}" - } - ] - }, - { - "name": "Get Version", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/version", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "version" - ] - }, - "description": "Get the current Testrun version and check if a software update is available" - }, - "response": [ - { - "name": "Get Version (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/version", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "version" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"installed_version\": \"v2.0\",\n \"update_available\": false,\n \"latest_version\": \"v2.0\",\n \"latest_version_url\": \"https://github.com/google/testrun/releases/tag/v2.0\"\n}" - } - ] - }, - { - "name": "Get Test Reports", - "protocolProfileBehavior": { - "disableBodyPruning": true - }, - "request": { - "method": "GET", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/reports", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "reports" - ] - }, - "description": "Get all previous Testrun reports" - }, - "response": [ - { - "name": "Get Test Reports (200)", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Origin", - "value": "http://localhost:8000", - "type": "text" - } - ], - "url": { - "raw": "localhost:8000/reports", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "reports" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occurred whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\",\n \"export\": \"http://localhost:8000/export/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" - }, - { - "name": "No Test Reports (200)", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Origin", - "value": "http://localhost:8000", - "type": "text" - } - ], - "url": { - "raw": "localhost:8000/reports", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "reports" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[]" - } - ] - }, - { - "name": "Delete Report", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2023-10-10 16:53:47\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/report", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report" - ] - }, - "description": "Delete a previous Testrun report" - }, - "response": [ - { - "name": "Report Deleted (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2024-08-05 13:37:53\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/report", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Deleted report\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/report", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - }, - { - "name": "Report Not Found (404))", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2023-10-10 16:53:47\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/report", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Report not found\"\n}" - } - ] - }, - { - "name": "Get Report", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/report/{device_name}/{timestamp}", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report", - "{device_name}", - "{timestamp}" - ] - }, - "description": "Get the PDF report for a specific device and timestamp" - }, - "response": [ - { - "name": "Get Report (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/report/Teltonika TRB140/2000-01-01T00:00:00", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report", - "Teltonika TRB140", - "2000-01-01T00:00:00" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Content-Type", - "value": "application/pdf", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "report.pdf" - }, - { - "name": "Report Not Found (404)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/report/Teltonika TRB140/2024-05-05 13:37:53", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "report", - "Teltonika TRB140", - "2024-05-05 13:37:53" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Report could not be found\"\n}" - } - ] - }, - { - "name": "Export Report", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"profile\": \"Primary profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/export/{device_name}/{timestamp}", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "export", - "{device_name}", - "{timestamp}" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - }, - "description": "Export the ZIP file of a specific device testing report" - }, - "response": [ - { - "name": "Export without profile (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/export/Teltonika TRB140/2024-08-05T13:37:53", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "export", - "Teltonika TRB140", - "2024-08-05T13:37:53" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Content-Type", - "value": "application/zip", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "report.zip" - }, - { - "name": "Export with profile (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/export/Teltonika TRB140/2024-08-05T13:37:53", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "export", - "Teltonika TRB140", - "2024-08-05T13:37:53" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "Text", - "header": [ - { - "key": "Content-Type", - "value": "application/zip", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "report.zip" - }, - { - "name": "Profile Not Found (404)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"profile\": \"Non-existing profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/export/Teltonika TRB140/2024-06-13T11:31:28", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "export", - "Teltonika TRB140", - "2024-06-13T11:31:28" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" - }, - { - "name": "Report Not Found (404)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/export/Teltonika TRB140/2020-08-05T13:37:53", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "export", - "Teltonika TRB140", - "2020-08-05T13:37:53" - ], - "query": [ - { - "key": "", - "value": null, - "disabled": true - } - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Report not found\"\n}" - } - ] - }, - { - "name": "Get Devices", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/devices", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "devices" - ] - }, - "description": "Obtain the list of devices from the device repository" - }, - "response": [ - { - "name": "Get Devices (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/devices", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "devices" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n {\n \"folder_url\": \"local/devices/Teltonika TRB140\",\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n },\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Teltonika TRB140\",\n \"reports\": [\n {\n \"_device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"_mac_addr\": \"00:1e:42:35:73:c4\",\n \"_status\": \"Non-Compliant\",\n \"_started\": \"2024-08-05T13:37:53\",\n \"_finished\": \"2024-08-05T13:39:35\",\n \"_total_tests\": 12,\n \"_results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"TLS 1.2 client connections valid\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Compliant\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"TLS 1.3 client connections valid\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n }\n ],\n \"_module_reports\": [],\n \"_report_url\": \"http://localhost:8000/report/Teltonika TRB140/2024-08-05T13:37:53\",\n \"_cur_page\": 0,\n \"_version\": \"1.3.1\"\n }\n ],\n \"max_device_reports\": null\n },\n {\n \"folder_url\": \"local/devices/Google First\",\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Google\",\n \"model\": \"First\",\n \"test_modules\": null,\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Google First\",\n \"reports\": [],\n \"max_device_reports\": null\n },\n {\n \"folder_url\": \"local/devices/Delta O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n }\n },\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Delta O3-DIN-CPU\",\n \"reports\": [],\n \"max_device_reports\": null\n }\n]" - }, - { - "name": "No Devices (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/devices", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "devices" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[]" - } - ] - }, - { - "name": "Create Device", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:55\",\n \"manufacturer\": \"TEST\",\n \"model\": \"Stest2\",\n \"type\": \"IoT Gateway\",\n \"technology\": \"Hardware - Access Control\",\n \"test_pack\": \"Device Qualification\",\n \"additional_info\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"Please select the technology this device falls into\",\n \"answer\": \"Hardware - Access Control\"\n },\n {\n \"question\": \"Does your device process any sensitive information? \",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is there a second IP port on the device?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"answer\": \"Yes\"\n }\n ],\n \"test_modules\": {\n \"protocol\": { \"enabled\": true },\n \"services\": { \"enabled\": true },\n \"ntp\": { \"enabled\": true },\n \"tls\": { \"enabled\": true },\n \"connection\": { \"enabled\": true },\n \"dns\": { \"enabled\": true }\n }}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - }, - "description": "Create a new device to test" - }, - "response": [ - { - "name": "Create Device (201)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Created", - "code": 201, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": null\n}" - }, - { - "name": "Already Exists (409)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Conflict", - "code": 409, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\"\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid JSON received\"\n}" - }, - { - "name": "Invalid Request (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid JSON received\"\n}" - } - ] - }, - { - "name": "Delete device", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - }, - "description": "Create a new device to test" - }, - "response": [ - { - "name": "Delete device (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully deleted the device\"\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"non-existing\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Device not found\"\n}" - }, - { - "name": "No Mac (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - }, - { - "name": "Testrun in Progress (403)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device" - ] - } - }, - "status": "Forbidden", - "code": 403, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Cannot delete this device whilst it is being tested\"\n}" - } - ] - }, - { - "name": "Edit Device", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c1\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n },\n \"dns\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": false\n }\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - }, - "description": "Update the configuration for a device" - }, - "response": [ - { - "name": "Edit Device (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n }\n }\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"non-existing\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A device with that MAC address could not be found\"\n}" - }, - { - "name": "Mac Address in Use (409)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - } - }, - "status": "Conflict", - "code": 409, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}" - }, - { - "name": "Device is Being Tested (403)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - } - }, - "status": "Forbidden", - "code": 403, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Cannot edit this device whilst it is being tested\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n \"model\": \"O3-DIN-CPU\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "localhost:8000/device/edit", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "device", - "edit" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - } - ] - }, - { - "name": "Get Devices Format", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/devices/format", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "devices", - "format" - ] - }, - "description": "Obtain the list of devices from the device repository" - }, - "response": [ - { - "name": "Get Devices Format (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/devices/format", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "devices", - "format" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n {\n \"step\": 1,\n \"title\": \"Select device type & technology\",\n \"description\": \"Before your device can go through testing, tell us more about your device and its functionality. It is important that we fully understand your device before a thorough assessment can be made.\",\n \"questions\": [\n {\n \"id\": 1,\n \"question\": \"What type of device is this?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Building Automation Gateway\",\n \"risk\": \"High\",\n \"id\": 1\n },\n {\n \"text\": \"IoT Gateway\",\n \"risk\": \"High\",\n \"id\": 2\n },\n {\n \"text\": \"Controller - AHU\",\n \"risk\": \"High\",\n \"id\": 3\n },\n {\n \"text\": \"Controller - Boiler\",\n \"risk\": \"High\",\n \"id\": 4\n },\n {\n \"text\": \"Controller - Chiller\",\n \"risk\": \"High\",\n \"id\": 5\n },\n {\n \"text\": \"Controller - FCU\",\n \"risk\": \"Limited\",\n \"id\": 6\n },\n {\n \"text\": \"Controller - Pump\",\n \"risk\": \"Limited\",\n \"id\": 7\n },\n {\n \"text\": \"Controller - CRAC\",\n \"risk\": \"High\",\n \"id\": 8\n },\n {\n \"text\": \"Controller - VAV\",\n \"risk\": \"Limited\",\n \"id\": 9\n },\n {\n \"text\": \"Controller - VRF\",\n \"risk\": \"Limited\",\n \"id\": 10\n },\n {\n \"text\": \"Controller - Multiple\",\n \"risk\": \"High\",\n \"id\": 11\n },\n {\n \"text\": \"Controller - Other\",\n \"risk\": \"High\",\n \"id\": 12\n },\n {\n \"text\": \"Controller - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 13\n },\n {\n \"text\": \"Controller - Blinds/Facades\",\n \"risk\": \"High\",\n \"id\": 14\n },\n {\n \"text\": \"Controller - Lifts/Elevators\",\n \"risk\": \"High\",\n \"id\": 15\n },\n {\n \"text\": \"Controller - UPS\",\n \"risk\": \"High\",\n \"id\": 16\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 17\n },\n {\n \"text\": \"Sensor - Vibration\",\n \"risk\": \"Limited\",\n \"id\": 18\n },\n {\n \"text\": \"Sensor - Humidity\",\n \"risk\": \"Limited\",\n \"id\": 19\n },\n {\n \"text\": \"Sensor - Water\",\n \"risk\": \"Limited\",\n \"id\": 20\n },\n {\n \"text\": \"Sensor - Occupancy\",\n \"risk\": \"High\",\n \"id\": 21\n },\n {\n \"text\": \"Sensor - Volume\",\n \"risk\": \"Limited\",\n \"id\": 22\n },\n {\n \"text\": \"Sensor - Weight\",\n \"risk\": \"Limited\",\n \"id\": 23\n },\n {\n \"text\": \"Sensor - Weather\",\n \"risk\": \"Limited\",\n \"id\": 24\n },\n {\n \"text\": \"Sensor - Steam\",\n \"risk\": \"High\",\n \"id\": 25\n },\n {\n \"text\": \"Sensor - Air Flow\",\n \"risk\": \"Limited\",\n \"id\": 26\n },\n {\n \"text\": \"Sensor - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 27\n },\n {\n \"text\": \"Sensor - Other\",\n \"risk\": \"High\",\n \"id\": 28\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 29\n },\n {\n \"text\": \"Monitoring - Fire System\",\n \"risk\": \"Limited\",\n \"id\": 30\n },\n {\n \"text\": \"Monitoring - Emergency Lighting\",\n \"risk\": \"Limited\",\n \"id\": 31\n },\n {\n \"text\": \"Monitoring - Other\",\n \"risk\": \"High\",\n \"id\": 32\n },\n {\n \"text\": \"Monitoring - UPS\",\n \"risk\": \"Limited\",\n \"id\": 33\n },\n {\n \"text\": \"Meter - Water\",\n \"risk\": \"Limited\",\n \"id\": 34\n },\n {\n \"text\": \"Meter - Gas\",\n \"risk\": \"Limited\",\n \"id\": 35\n },\n {\n \"text\": \"Meter - Electricity\",\n \"risk\": \"Limited\",\n \"id\": 36\n },\n {\n \"text\": \"Meter - Other\",\n \"risk\": \"High\",\n \"id\": 37\n },\n {\n \"text\": \"Other\",\n \"risk\": \"High\",\n \"id\": 38\n },\n {\n \"text\": \"Data - Storage\",\n \"risk\": \"High\",\n \"id\": 39\n },\n {\n \"text\": \"Data - Processing\",\n \"risk\": \"High\",\n \"id\": 40\n },\n {\n \"text\": \"Tablet\",\n \"risk\": \"High\",\n \"id\": 41\n }\n ]\n },\n {\n \"id\": 2,\n \"question\": \"Please select the technology this device falls into\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Hardware - Access Control\"\n },\n {\n \"id\": 2,\n \"text\": \"Hardware - Air quality\"\n },\n {\n \"id\": 3,\n \"text\": \"Hardware - Asset location tracking\"\n },\n {\n \"id\": 4,\n \"text\": \"Hardware - Audio Visual\"\n },\n {\n \"id\": 5,\n \"text\": \"Hardware - Blinds/Facade\"\n },\n {\n \"id\": 6,\n \"text\": \"Hardware - Cameras\"\n },\n {\n \"id\": 7,\n \"text\": \"Hardware - Catering\"\n },\n {\n \"id\": 8,\n \"text\": \"Hardware - Data Ingestion/Managment\"\n },\n {\n \"id\": 9,\n \"text\": \"Hardware - EV Charging\"\n },\n {\n \"id\": 10,\n \"text\": \"Hardware - Fitness\"\n },\n {\n \"id\": 11,\n \"text\": \"Hardware - HVAC\"\n },\n {\n \"id\": 12,\n \"text\": \"Hardware - Irrigation\"\n },\n {\n \"id\": 13,\n \"text\": \"Hardware - Leak Detection\"\n },\n {\n \"id\": 14,\n \"text\": \"Hardware - Lifts/Elevators\"\n },\n {\n \"id\": 15,\n \"text\": \"Hardware - Lighting\"\n },\n {\n \"id\": 16,\n \"text\": \"Hardware - Metering\"\n },\n {\n \"id\": 17,\n \"text\": \"Hardware - Monitoring\"\n },\n {\n \"id\": 18,\n \"text\": \"Hardware - Occupancy\"\n },\n {\n \"id\": 19,\n \"text\": \"Hardware - System Integration\"\n },\n {\n \"id\": 20,\n \"text\": \"Hardware - Time Management\"\n },\n {\n \"id\": 21,\n \"text\": \"Hardware - UPS\"\n },\n {\n \"id\": 22,\n \"text\": \"Hardware - Waste Management\"\n },\n {\n \"id\": 23,\n \"text\": \"Building Automation\"\n },\n {\n \"id\": 24,\n \"text\": \"Other\"\n }\n ]\n },\n {\n \"id\": 3,\n \"question\": \"Does your device process any sensitive information? \",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"id\": 2,\n \"text\": \"No\",\n \"risk\": \"High\"\n },\n {\n \"id\": 3,\n \"text\": \"I don't know\",\n \"risk\": \"High\"\n }\n ]\n }\n ]\n },\n {\n \"step\": 2,\n \"title\": \"Tell us more about your device\",\n \"description\": \"Before your device can go through testing, tell us more about your device and its functionality. It is important that we fully understand your device before a thorough assessment can be made.\",\n \"questions\": [\n {\n \"id\": 1,\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 2,\n \"question\": \"Is there a second IP port on the device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 3,\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n },\n {\n \"text\": \"N/A\",\n \"id\": 3\n }\n ]\n }\n ]\n }\n]" - } - ] - }, - { - "name": "Get System Modules", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/modules", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "modules" - ] - }, - "description": "Get a list of all root CA certificates that have been loaded into Testrun" - }, - "response": [ - { - "name": "Get System Modules (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/modules", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "modules" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n \"Protocol\",\n \"Services\",\n \"Connection\",\n \"TLS\",\n \"NTP\",\n \"DNS\"\n]" - } - ] - }, - { - "name": "Get System Testpacks", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/testpacks", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "testpacks" - ] - }, - "description": "Obtain a list of applicable and available network adapters for use in testing" - }, - "response": [ - { - "name": "System Testpacks (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "localhost:8000/system/testpacks", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "testpacks" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n \"Pilot Assessment\",\n \"Device Qualification\"\n]" - } - ] - }, - { - "name": "List Certificates", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - }, - "description": "Get a list of all root CA certificates that have been loaded into Testrun" - }, - "response": [ - { - "name": "List Certificates (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[\n {\n \"name\": \"GTS Root R1\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2036-06-22T00:00:00+00:00\",\n \"filename\": \"crt.pem\"\n },\n {\n \"name\": \"WR2\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2029-02-20T14:00:00+00:00\",\n \"filename\": \"WR2.pem\"\n }\n]" - }, - { - "name": "No Certificates (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "[]" - } - ] - }, - { - "name": "Upload Certificate", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "NH1mNzqEQ/1c3.pem" - } - ] - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - }, - "description": "Upload a new root CA certificate into Testrun" - }, - "response": [ - { - "name": "Upload Certificate (201)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "NH1mNzqEQ/1c3.pem" - } - ] - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Created", - "code": 201, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"name\": \"GTS Root R1\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2036-06-22T00:00:00+00:00\",\n \"filename\": \"crt.pem\"\n}" - }, - { - "name": "Duplicate (409)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "NH1mNzqEQ/1c3.pem" - } - ] - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Conflict", - "code": 409, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A certificate with that file name already exists.\"\n}" - }, - { - "name": "Invalid Cert (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "formdata", - "formdata": [ - { - "key": "file", - "type": "file", - "src": "postman-cloud:///1ef5fb1a-6342-44e0-b35f-700d496e0cc5" - } - ] - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Failed to upload certificate. Is it in the correct format?\"\n}" - } - ] - }, - { - "name": "Delete Certificate", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"GTS CA 1C3\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - }, - "description": "Delete a root CA certificate" - }, - "response": [ - { - "name": "Delete Certificate (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"GTS Root R1\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully deleted the certificate\"\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing name\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Received a bad request\"\n}" - } - ] - }, - { - "name": "Get Profile Format", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles/format", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles", - "format" - ] - }, - "description": "Obtain the current format of the risk assessment questionnaire" - }, - "response": [ - { - "name": "Get Profile Format (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles/format", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles", - "format" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Desribe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\",\n \"required\": true\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device does not encrypt data during transmission\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is not required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\"\n }\n }\n]" - } - ] - }, - { - "name": "List Profiles", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Get a list of risk assessment profiles saved by the user" - }, - "response": [ - { - "name": "List Profiles (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]" - }, - { - "name": "No Profiles (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n \n]" - } - ] - }, - { - "name": "Export Profile", - "request": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/profile/{profile_name}", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "{profile_name}" - ] - }, - "description": "Get the PDF report for a specific device and timestamp" - }, - "response": [ - { - "name": "Get Profile No Device (200)", - "originalRequest": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/profile/Test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "Test" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Content-Type", - "value": "application/pdf", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "profile.pdf" - }, - { - "name": "Get Profile with Device (200))", - "originalRequest": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\"mac_addr\": \"00:1e:42:35:73:c4\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profile/Test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "Test" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "text", - "header": [ - { - "key": "Content-Type", - "value": "application/pdf", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "profile.pdf" - }, - { - "name": "Profile Not Found (404)", - "originalRequest": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/profile/NonExistingProfile", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "NonExistingProfile" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Profile could not be found\"\n}" - }, - { - "name": "Device Not Found (404)", - "originalRequest": { - "method": "GET", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\"mac_addr\": \"non_existing_mac_addr\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profile/Test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "Test" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A device with that mac address could not be found\"\n}" - }, - { - "name": "Internal Server Error (500) Copy", - "originalRequest": { - "method": "POST", - "header": [], - "url": { - "raw": "http://localhost:8000/profile/Test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "Test" - ] - } - }, - "status": "Internal Server Error", - "code": 500, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}" - } - ] - }, - { - "name": "Update Profile", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Testing\",\n \"status\": \"Valid\",\n \"questions\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"asdasd\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"N/A\"\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"answer\": [\n 3\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 6\n ]\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Create or update a risk assessment questionnaire response" - }, - "response": [ - { - "name": "Update Profile (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "\n{\n \"name\": \"New Profile\",\n \"rename\": \"Updated New Profile\",\n \"version\": \"2.0.1\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully updated that profile\"\n}" - }, - { - "name": "Create Profile (201)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "\n{\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}\n", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Created", - "code": 201, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully created a new profile\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - }, - { - "name": "Not Implemented (501)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Not Implemented", - "code": 501, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Risk profiles are not available right now\"\n}" - } - ] - }, - { - "name": "Delete Profile", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"New Profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Delete an existing risk assessment questionaire response" - }, - "response": [ - { - "name": "Delete Profile (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"New Profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully deleted that profile\"\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" - }, - { - "name": "Internal Serves Error (500)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Internal Server Error", - "code": 500, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"An error occurred whilst deleting that profile\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - } - ] - }, - { - "name": "http://localhost:8000/profile/Test", - "request": { - "method": "POST", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "type": "text" - } - ], - "body": { - "mode": "raw", - "raw": "{\"mac_addr\": \"non_existing_mac_addr\"}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profile/Test", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profile", - "Test" - ] - }, - "description": "Delete a root CA certificate" - }, - "response": [ - { - "name": "Delete Certificate (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"GTS Root R1\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully deleted the certificate\"\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing name\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/system/config/certs", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "system", - "config", - "certs" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Received a bad request\"\n}" - } - ] - }, - { - "name": "Get Profile Format", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles/format", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles", - "format" - ] - }, - "description": "Obtain the current format of the risk assessment questionnaire" - }, - "response": [ - { - "name": "Get Profile Format (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles/format", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles", - "format" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Desribe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\",\n \"required\": true\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device does not encrypt data during transmission\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is not required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\"\n }\n }\n]" - } - ] - }, - { - "name": "List Profiles", - "request": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Get a list of risk assessment profiles saved by the user" - }, - "response": [ - { - "name": "List Profiles (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]" - }, - { - "name": "No Profiles (200)", - "originalRequest": { - "method": "GET", - "header": [], - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "" - } - ], - "cookie": [], - "body": "[\n \n]" - } - ] - }, - { - "name": "Update Profile", - "request": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"Testing\",\n \"status\": \"Valid\",\n \"questions\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"asdasd\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"N/A\"\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"answer\": [\n 3\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 6\n ]\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Create or update a risk assessment questionnaire response" - }, - "response": [ - { - "name": "Update Profile (200)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully updated that profile\"\n}" - }, - { - "name": "Create Profile (201)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Created", - "code": 201, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully created a new profile\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - }, - { - "name": "Not Implemented (501)", - "originalRequest": { - "method": "POST", - "header": [], - "body": { - "mode": "raw", - "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Not Implemented", - "code": 501, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Risk profiles are not available right now\"\n}" - } - ] - }, - { - "name": "Delete Profile", - "request": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"New Profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - }, - "description": "Delete an existing risk assessment questionaire response" - }, - "response": [ - { - "name": "Delete Profile (200)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"New Profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "OK", - "code": 200, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"success\": \"Successfully deleted that profile\"\n}" - }, - { - "name": "Not Found (404)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Not Found", - "code": 404, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" - }, - { - "name": "Internal Serves Error (500)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n \"name\": \"non-existing profile\"\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Internal Server Error", - "code": 500, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"An error occurred whilst deleting that profile\"\n}" - }, - { - "name": "Invalid JSON (400)", - "originalRequest": { - "method": "DELETE", - "header": [], - "body": { - "mode": "raw", - "raw": "{\n\n}", - "options": { - "raw": { - "language": "json" - } - } - }, - "url": { - "raw": "http://localhost:8000/profiles", - "protocol": "http", - "host": [ - "localhost" - ], - "port": "8000", - "path": [ - "profiles" - ] - } - }, - "status": "Bad Request", - "code": 400, - "_postman_previewlanguage": "json", - "header": [ - { - "key": "Content-Type", - "value": "application/json", - "name": "Content-Type", - "description": "", - "type": "text" - } - ], - "cookie": [], - "body": "{\n \"error\": \"Invalid request received\"\n}" - } - ] - } - ] + "info": { + "_postman_id": "aed14372-bc32-40fe-9685-4013c309614a", + "name": "Testrun", + "description": "API endpoints for the Testrun API", + "schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json", + "_exporter_id": "9516055" + }, + "item": [ + { + "name": "Get System Interfaces", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/interfaces", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "interfaces" + ] + }, + "description": "Obtain a list of applicable and available network adapters for use in testing" + }, + "response": [ + { + "name": "Interfaces Available (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/interfaces", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "interfaces" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"enx00e04c020fa8\": \"00:e0:4c:02:0f:a8\",\n \"enx207bd26205e9\": \"20:7b:d2:62:05:e9\"\n}" + }, + { + "name": "No interfaces (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/interfaces", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "interfaces" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{}" + } + ] + }, + { + "name": "Get Configuration", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/config", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config" + ] + }, + "description": "Get the current system configuration" + }, + "response": [ + { + "name": "Get Configuration (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/config", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"network\": {\n \"device_intf\": \"enx207bd2620617\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"INFO\",\n \"startup_timeout\": 60,\n \"monitor_period\": 60,\n \"runtime\": 120\n}" + } + ] + }, + { + "name": "Set Configuration", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"network\": {\n \"device_intf\": \"enx207bd2620617\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"startup_timeout\": 60,\n \"monitor_period\": 20,\n \"runtime\": 120,\n \"org_name\": \"Google\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/config", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config" + ] + }, + "description": "Update the current system configuration" + }, + "response": [ + { + "name": "Configuration Set (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120, // Optional\n \"startup_timeout\": 60, // Optional\n \"monitor_period\": 60 // Optional\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/config", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120,\n \"startup_timeout\": 60,\n \"monitor_period\": 60\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"network\": {\n \"device_intf\": \"enx00e04c020fa8\",\n \"internet_intf\": \"enx207bd26205e9\"\n },\n \"log_level\": \"DEBUG\",\n \"runtime\": 120,\n \"startup_timeout\": 60\n \"monitor_period\": 60 \n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/config", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid JSON received\"\n}" + } + ] + }, + { + "name": "Start Testrun", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + }, + "description": "Start Testrun against a target device" + }, + "response": [ + { + "name": "Starting (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-07-18T11:14:42.917670\",\n \"finished\": null, \n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" + }, + { + "name": "Invalid Request (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"firmware\": \"1.2.2\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:ff,\n \"firmware\": \"1.2.2\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid JSON received\"\n}" + }, + { + "name": "Device Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"aa:bb:cc:dd:ee:99\",\n \"firmware\": \"1.2.2\"\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that MAC address could not be found\"\n}" + }, + { + "name": "Testrun Already in Progress (409)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/start", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "start" + ] + } + }, + "status": "Conflict", + "code": 409, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Testrun cannot be started whilst a test is running on another device\"\n}" + } + ] + }, + { + "name": "Stop Testrun", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/stop", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "stop" + ] + }, + "description": "Stop Testrun from running against a device" + }, + "response": [ + { + "name": "Stopped (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/stop", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "stop" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Testrun stopped\"\n}" + }, + { + "name": "Not Running (404)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/stop", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "stop" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Testrun is not currently running\"\n}" + } + ] + }, + { + "name": "Get System Status", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + }, + "description": "Get the current status of Testrun. Suggested that this is called every 5 seconds to capture most events" + }, + "response": [ + { + "name": "Monitoring (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Monitoring\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" + }, + { + "name": "Test in Progress (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"In Progress\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Non-Compliant\"\n }\n ]\n }\n}" + }, + { + "name": "Cancelled (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Cancelled\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:24:00.123Z\",\n \"report\": null,\n \"tests\": {\n \"total\": 22,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n }\n ]\n }\n}" + }, + { + "name": "Waiting for Device (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Waiting for Device\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": null,\n \"report\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" + }, + { + "name": "Non-Compliant (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Non-Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 26,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Non-Compliant\",\n \"required_result\": \"Compliant\"\n \"recommendations\": [\n \"An example of a step to resolve\",\n \"Disable any running NTP server\"\n ]\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Compliant\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}" + }, + { + "name": "Compliant (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Compliant\",\n \"device\": {\n \"manufacturer\": \"Delta\",\n \"model\": \"03-DIN-CPU\",\n \"mac_addr\": \"01:02:03:04:05:06\",\n \"firmware\": \"1.2.2\"\n },\n \"started\": \"2023-06-22T09:20:00.123Z\",\n \"finished\": \"2023-06-22T09:26:00.123Z\",\n \"report\": \"https://api.testrun.io/report.pdf\",\n \"tests\": {\n \"total\": 3,\n \"results\": [\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"The device should resolve hostnames\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"The device should use the DNS server provided by the DHCP server\",\n \"result\": \"Feature Not Present\",\n \"required_result\": \"Roadmap\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"FTP server should not be available\",\n \"result\": \"Compliant\",\n \"required_result\": \"Required\"\n }\n ]\n }\n}" + }, + { + "name": "Not Running - Idle (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/status", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "status" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"status\": \"Idle\",\n \"device\": null,\n \"started\": null,\n \"finished\": null,\n \"tests\": {\n \"total\": 0,\n \"results\": []\n }\n}" + } + ] + }, + { + "name": "Shutdown Testrun", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/system/shutdown", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "shutdown" + ] + }, + "description": "Stop the Testrun framework" + }, + "response": [ + { + "name": "Shutdown Testrun (200)", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/system/shutdown", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "shutdown" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "null" + }, + { + "name": "Test in Progress (400)", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"firmware\": \"1.2.2\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/shutdown", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "shutdown" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Unable to shutdown. A test is currently in progress.\"\n}" + } + ] + }, + { + "name": "Get Version", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/version", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "version" + ] + }, + "description": "Get the current Testrun version and check if a software update is available" + }, + "response": [ + { + "name": "Get Version (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/version", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "version" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"installed_version\": \"v2.0\",\n \"update_available\": false,\n \"latest_version\": \"v2.0\",\n \"latest_version_url\": \"https://github.com/google/testrun/releases/tag/v2.0\"\n}" + } + ] + }, + { + "name": "Get Test Reports", + "protocolProfileBehavior": { + "disableBodyPruning": true + }, + "request": { + "method": "GET", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/reports", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "reports" + ] + }, + "description": "Get all previous Testrun reports" + }, + "response": [ + { + "name": "Get Test Reports (200)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Origin", + "value": "http://localhost:8000", + "type": "text" + } + ], + "url": { + "raw": "localhost:8000/reports", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "reports" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n {\n \"testrun\": {\n \"version\": \"2.0\"\n },\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:28:9e:4a\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"test\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"tls\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"status\": \"Non-Compliant\",\n \"started\": \"2000-01-01 00:00:00\",\n \"finished\": \"2000-01-01 00:30:00\",\n \"tests\": {\n \"total\": 40,\n \"results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.services.ftp\",\n \"description\": \"No FTP server found\",\n \"expected_behavior\": \"There is no FTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.ssh.version\",\n \"description\": \"SSH server found running protocol 2.0\",\n \"expected_behavior\": \"SSH server is not running or server is SSHv2\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.telnet\",\n \"description\": \"No telnet server found\",\n \"expected_behavior\": \"There is no Telnet service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.smtp\",\n \"description\": \"No SMTP server found\",\n \"expected_behavior\": \"There is no SMTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.http\",\n \"description\": \"Found HTTP server running on port 80/tcp\",\n \"expected_behavior\": \"Device is unreachable on port 80 (or any other port) and only responds to HTTPS requests on port 443 (or any other port if HTTP is used at all)\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Disable all unsecure HTTP servers\",\n \"Setup TLS on the web server\"\n ]\n },\n {\n \"name\": \"security.services.pop\",\n \"description\": \"No POP server found\",\n \"expected_behavior\": \"There is no POP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.imap\",\n \"description\": \"No IMAP server found\",\n \"expected_behavior\": \"There is no IMAP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.snmpv3\",\n \"description\": \"No SNMP server found\",\n \"expected_behavior\": \"Device is unreachable on port 161 (or any other port) and device is unreachable on port 162 (or any other port) unless SNMP is essential in which case it is SNMPv3 is used.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.vnc\",\n \"description\": \"No VNC server found\",\n \"expected_behavior\": \"Device cannot be accessed / connected to via VNC on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"security.services.tftp\",\n \"description\": \"No TFTP server found\",\n \"expected_behavior\": \"There is no TFTP service running on any port\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"ntp.network.ntp_server\",\n \"description\": \"No NTP server found\",\n \"expected_behavior\": \"The device does not respond to NTP requests when it's IP is set as the NTP server on another device\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_link\",\n \"description\": \"No port errors detected\",\n \"expected_behavior\": \"When the etherent cable is connected to the port, the device triggers the port to its enabled \\\"Link UP\\\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \\\"show interface\\\" command on most network switches.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_speed\",\n \"description\": \"Succesfully auto-negotiated speeds above 10 Mbps\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \\\"show interface\\\" command on most network switches. The output of this command must also show that the \\\"configured speed\\\" is set to \\\"auto\\\".\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.port_duplex\",\n \"description\": \"Succesfully auto-negotiated full duplex\",\n \"expected_behavior\": \"When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.arp_inspection\",\n \"description\": \"Device uses ARP\",\n \"expected_behavior\": \"Device continues to operate correctly when ARP inspection is enabled on the switch. No functionality is lost with ARP inspection enabled.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.switch.dhcp_snooping\",\n \"description\": \"Device does not act as a DHCP server\",\n \"expected_behavior\": \"Device continues to operate correctly when DHCP snooping is enabled on the switch.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_address\",\n \"description\": \"Device responded to leased ip address\",\n \"expected_behavior\": \"The device is not setup with a static IP address. The device accepts an IP address from a DHCP server (RFC 2131) and responds succesfully to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_address\",\n \"description\": \"MAC address found: 00:1e:42:28:9e:4a\",\n \"expected_behavior\": \"N/A\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.mac_oui\",\n \"description\": \"OUI Manufacturer found: Teltonika\",\n \"expected_behavior\": \"The MAC address prefix is registered in the IEEE Organizationally Unique Identifier database.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.private_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within all ranges specified in RFC 1918 and communicates using these addresses. The Internet Assigned Numbers Authority (IANA) has reserved the following three blocks of the IP address space for private internets. 10.0.0.0 - 10.255.255.255.255 (10/8 prefix). 172.16.0.0 - 172.31.255.255 (172.16/12 prefix). 192.168.0.0 - 192.168.255.255 (192.168/16 prefix)\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.shared_address\",\n \"description\": \"All subnets are supported\",\n \"expected_behavior\": \"The device under test accepts IP addresses within the ranges specified in RFC 6598 and communicates using these addresses\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.dhcp_disconnect\",\n \"description\": \"An error occurred whilst running this test\",\n \"expected_behavior\": \"A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network addres until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Error\"\n },\n {\n \"name\": \"connection.single_ip\",\n \"description\": \"Device is using multiple IP addresses\",\n \"expected_behavior\": \"The device under test does not behave as a network switch and only requets one IP address. This test is to avoid that devices implement network switches that allow connecting strings of daisy chained devices to one single network port, as this would not make 802.1x port based authentication possible.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Ensure that all ports on the device are isolated\",\n \"Ensure only one DHCP client is running\"\n ]\n },\n {\n \"name\": \"connection.target_ping\",\n \"description\": \"Device responds to ping\",\n \"expected_behavior\": \"The device under test responds to an ICMP echo (ping) request.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.ip_change\",\n \"description\": \"Device has accepted an IP address change\",\n \"expected_behavior\": \"If the lease expires before the client receiveds a DHCPACK, the client moves to INIT state, MUST immediately stop any other network processing and requires network initialization parameters as if the client were uninitialized. If the client then receives a DHCPACK allocating the client its previous network addres, the client SHOULD continue network processing. If the client is given a new network address, it MUST NOT continue using the previous network address and SHOULD notify the local users of the problem.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipaddr.dhcp_failover\",\n \"description\": \"Secondary DHCP server lease confirmed active in device\",\n \"expected_behavior\": \"\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"connection.ipv6_slaac\",\n \"description\": \"Device does not support IPv6 SLAAC\",\n \"expected_behavior\": \"The device under test complies with RFC4862 and forms a valid IPv6 SLAAC address\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Install a network manager that supports IPv6\",\n \"Disable DHCPv6\"\n ]\n },\n {\n \"name\": \"connection.ipv6_ping\",\n \"description\": \"No IPv6 SLAAC address found. Cannot ping\",\n \"expected_behavior\": \"The device responds to the ping as per RFC4443\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable ping response to IPv6 ICMP requests in network manager settings\",\n \"Create a firewall exception to allow ICMPv6 via LAN\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"No outbound TLS connections were found\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device has not sent any NTP requests\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\"\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\"\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\"\n }\n ]\n },\n \"report\": \"http://localhost:8000/report/Teltonika TRB140/2024-09-10T13:19:24\",\n \"export\": \"http://localhost:8000/export/Teltonika TRB140/2024-09-10T13:19:24\"\n }\n]" + }, + { + "name": "No Test Reports (200)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Origin", + "value": "http://localhost:8000", + "type": "text" + } + ], + "url": { + "raw": "localhost:8000/reports", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "reports" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[]" + } + ] + }, + { + "name": "Delete Report", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2023-10-10 16:53:47\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/report", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report" + ] + }, + "description": "Delete a previous Testrun report" + }, + "response": [ + { + "name": "Report Deleted (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2024-08-05 13:37:53\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/report", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Deleted report\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/report", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Report Not Found (404))", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"timestamp\": \"2023-10-10 16:53:47\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/report", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Report not found\"\n}" + } + ] + }, + { + "name": "Get Report", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/report/{device_name}/{timestamp}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report", + "{device_name}", + "{timestamp}" + ] + }, + "description": "Get the PDF report for a specific device and timestamp" + }, + "response": [ + { + "name": "Get Report (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/report/Teltonika TRB140/2000-01-01T00:00:00", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report", + "Teltonika TRB140", + "2000-01-01T00:00:00" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/pdf", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "report.pdf" + }, + { + "name": "Report Not Found (404)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/report/Teltonika TRB140/2024-05-05 13:37:53", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "report", + "Teltonika TRB140", + "2024-05-05 13:37:53" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Report could not be found\"\n}" + } + ] + }, + { + "name": "Export Report", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"profile\": \"Primary profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/export/{device_name}/{timestamp}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "export", + "{device_name}", + "{timestamp}" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + }, + "description": "Export the ZIP file of a specific device testing report" + }, + "response": [ + { + "name": "Export without profile (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/export/Teltonika TRB140/2024-08-05T13:37:53", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "export", + "Teltonika TRB140", + "2024-08-05T13:37:53" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/zip", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "report.zip" + }, + { + "name": "Export with profile (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/export/Teltonika TRB140/2024-08-05T13:37:53", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "export", + "Teltonika TRB140", + "2024-08-05T13:37:53" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "Text", + "header": [ + { + "key": "Content-Type", + "value": "application/zip", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "report.zip" + }, + { + "name": "Profile Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"profile\": \"Non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/export/Teltonika TRB140/2024-06-13T11:31:28", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "export", + "Teltonika TRB140", + "2024-06-13T11:31:28" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" + }, + { + "name": "Report Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/export/Teltonika TRB140/2020-08-05T13:37:53", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "export", + "Teltonika TRB140", + "2020-08-05T13:37:53" + ], + "query": [ + { + "key": "", + "value": null, + "disabled": true + } + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Report not found\"\n}" + } + ] + }, + { + "name": "Get Devices", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/devices", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "devices" + ] + }, + "description": "Obtain the list of devices from the device repository" + }, + "response": [ + { + "name": "Get Devices (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/devices", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "devices" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n {\n \"folder_url\": \"local/devices/Teltonika TRB140\",\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n },\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Teltonika TRB140\",\n \"reports\": [\n {\n \"_device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"firmware\": \"1.2.3\",\n \"test_modules\": {\n \"protocol\": {\n \"enabled\": true\n },\n \"services\": {\n \"enabled\": false\n },\n \"connection\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": true\n },\n \"dns\": {\n \"enabled\": true\n }\n }\n },\n \"_mac_addr\": \"00:1e:42:35:73:c4\",\n \"_status\": \"Non-Compliant\",\n \"_started\": \"2024-08-05T13:37:53\",\n \"_finished\": \"2024-08-05T13:39:35\",\n \"_total_tests\": 12,\n \"_results\": [\n {\n \"name\": \"protocol.valid_bacnet\",\n \"description\": \"BACnet device could not be discovered\",\n \"expected_behavior\": \"BACnet traffic can be seen on the network and packets are valid and not malformed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"protocol.bacnet.version\",\n \"description\": \"Device did not respond to BACnet discovery\",\n \"expected_behavior\": \"The BACnet client implements an up to date version of BACnet\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"protocol.valid_modbus\",\n \"description\": \"Device did not respond to Modbus connection\",\n \"expected_behavior\": \"Any Modbus functionality works as expected and valid Modbus traffic can be observed\",\n \"required_result\": \"Recommended\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_2_server\",\n \"description\": \"TLS 1.2 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.2 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Enable TLS 1.2 support in the web server configuration\",\n \"Disable TLS 1.0 and 1.1\",\n \"Sign the certificate used by the web server\"\n ]\n },\n {\n \"name\": \"security.tls.v1_2_client\",\n \"description\": \"TLS 1.2 client connections valid\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers\",\n \"required_result\": \"Required if Applicable\",\n \"result\": \"Compliant\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_3_server\",\n \"description\": \"TLS 1.3 certificate is invalid\",\n \"expected_behavior\": \"TLS 1.3 certificate is issued to the web browser client when accessed\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"security.tls.v1_3_client\",\n \"description\": \"TLS 1.3 client connections valid\",\n \"expected_behavior\": \"The packet indicates a TLS connection with at least TLS 1.3\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"ntp.network.ntp_support\",\n \"description\": \"Device sent NTPv3 packets. NTPv3 is not allowed\",\n \"expected_behavior\": \"The device sends an NTPv4 request to the configured NTP server.\",\n \"required_result\": \"Required\",\n \"result\": \"Non-Compliant\",\n \"recommendations\": [\n \"Set the NTP version to v4 in the NTP client\",\n \"Install an NTP client that supports NTPv4\"\n ]\n },\n {\n \"name\": \"ntp.network.ntp_dhcp\",\n \"description\": \"Device sent NTP request to non-DHCP provided server\",\n \"expected_behavior\": \"Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)\",\n \"required_result\": \"Roadmap\",\n \"result\": \"Feature Not Detected\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.network.hostname_resolution\",\n \"description\": \"DNS traffic detected from device\",\n \"expected_behavior\": \"The device sends DNS requests.\",\n \"required_result\": \"Required\",\n \"result\": \"Compliant\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.network.from_dhcp\",\n \"description\": \"DNS traffic detected only to DHCP provided server\",\n \"expected_behavior\": \"The device sends DNS requests to the DNS server provided by the DHCP server\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n },\n {\n \"name\": \"dns.mdns\",\n \"description\": \"No MDNS traffic detected from the device\",\n \"expected_behavior\": \"Device may send MDNS requests\",\n \"required_result\": \"Informational\",\n \"result\": \"Informational\",\n \"recommendations\": []\n }\n ],\n \"_module_reports\": [],\n \"_report_url\": \"http://localhost:8000/report/Teltonika TRB140/2024-08-05T13:37:53\",\n \"_cur_page\": 0,\n \"_version\": \"1.3.1\"\n }\n ],\n \"max_device_reports\": null\n },\n {\n \"folder_url\": \"local/devices/Google First\",\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Google\",\n \"model\": \"First\",\n \"test_modules\": null,\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Google First\",\n \"reports\": [],\n \"max_device_reports\": null\n },\n {\n \"folder_url\": \"local/devices/Delta O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n }\n },\n \"ip_addr\": null,\n \"firmware\": null,\n \"device_folder\": \"Delta O3-DIN-CPU\",\n \"reports\": [],\n \"max_device_reports\": null\n }\n]" + }, + { + "name": "No Devices (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/devices", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "devices" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[]" + } + ] + }, + { + "name": "Create Device", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:55\",\n \"manufacturer\": \"TEST\",\n \"model\": \"Stest2\",\n \"type\": \"IoT Gateway\",\n \"technology\": \"Hardware - Access Control\",\n \"test_pack\": \"Device Qualification\",\n \"additional_info\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"Please select the technology this device falls into\",\n \"answer\": \"Hardware - Access Control\"\n },\n {\n \"question\": \"Does your device process any sensitive information? \",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is there a second IP port on the device?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"answer\": \"Yes\"\n }\n ],\n \"test_modules\": {\n \"protocol\": { \"enabled\": true },\n \"services\": { \"enabled\": true },\n \"ntp\": { \"enabled\": true },\n \"tls\": { \"enabled\": true },\n \"connection\": { \"enabled\": true },\n \"dns\": { \"enabled\": true }\n }}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + }, + "description": "Create a new device to test" + }, + "response": [ + { + "name": "Create Device (201)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": null\n}" + }, + { + "name": "Already Exists (409)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Conflict", + "code": 409, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\"\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid JSON received\"\n}" + }, + { + "name": "Invalid Request (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid JSON received\"\n}" + } + ] + }, + { + "name": "Delete device", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + }, + "description": "Create a new device to test" + }, + "response": [ + { + "name": "Delete device (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted the device\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"non-existing\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Device not found\"\n}" + }, + { + "name": "No Mac (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Testrun in Progress (403)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Cannot delete this device whilst it is being tested\"\n}" + } + ] + }, + { + "name": "Edit Device", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c1\",\n \"manufacturer\": \"Teltonika\",\n \"model\": \"TRB140\",\n \"test_modules\": {\n \"connection\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n },\n \"dns\": {\n \"enabled\": false\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": false\n },\n \"tls\": {\n \"enabled\": false\n }\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + }, + "description": "Update the configuration for a device" + }, + "response": [ + { + "name": "Edit Device (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\n \"enabled\": true\n },\n \"connection\": {\n \"enabled\": true\n },\n \"ntp\": {\n \"enabled\": false\n },\n \"baseline\": {\n \"enabled\": true\n },\n \"nmap\": {\n \"enabled\": false\n }\n }\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"non-existing\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that MAC address could not be found\"\n}" + }, + { + "name": "Mac Address in Use (409)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + } + }, + "status": "Conflict", + "code": 409, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that MAC address already exists\"\n}" + }, + { + "name": "Device is Being Tested (403)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:0e:12:15:13:c8\",\n \"manufacturer\": \"Delta\",\n \"model\": \"O3-DIN-CPU\",\n \"test_modules\": {\n \"dns\": {\"enabled\": true},\n \"connection\": {\"enabled\": true},\n \"ntp\": {\"enabled\": false},\n \"baseline\": {\"enabled\": true},\n \"nmap\": {\"enabled\": false}\n }\n }\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + } + }, + "status": "Forbidden", + "code": 403, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Cannot edit this device whilst it is being tested\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"mac_addr\": \"00:1e:42:35:73:c4\",\n \"device\": {\n \"mac_addr\": \"00:1e:42:35:73:c4\"\n \"model\": \"O3-DIN-CPU\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "localhost:8000/device/edit", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "device", + "edit" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + } + ] + }, + { + "name": "Get Devices Format", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/devices/format", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "devices", + "format" + ] + }, + "description": "Obtain the list of devices from the device repository" + }, + "response": [ + { + "name": "Get Devices Format (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/devices/format", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "devices", + "format" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n {\n \"step\": 1,\n \"title\": \"Select device type & technology\",\n \"description\": \"Before your device can go through testing, tell us more about your device and its functionality. It is important that we fully understand your device before a thorough assessment can be made.\",\n \"questions\": [\n {\n \"id\": 1,\n \"question\": \"What type of device is this?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Building Automation Gateway\",\n \"risk\": \"High\",\n \"id\": 1\n },\n {\n \"text\": \"IoT Gateway\",\n \"risk\": \"High\",\n \"id\": 2\n },\n {\n \"text\": \"Controller - AHU\",\n \"risk\": \"High\",\n \"id\": 3\n },\n {\n \"text\": \"Controller - Boiler\",\n \"risk\": \"High\",\n \"id\": 4\n },\n {\n \"text\": \"Controller - Chiller\",\n \"risk\": \"High\",\n \"id\": 5\n },\n {\n \"text\": \"Controller - FCU\",\n \"risk\": \"Limited\",\n \"id\": 6\n },\n {\n \"text\": \"Controller - Pump\",\n \"risk\": \"Limited\",\n \"id\": 7\n },\n {\n \"text\": \"Controller - CRAC\",\n \"risk\": \"High\",\n \"id\": 8\n },\n {\n \"text\": \"Controller - VAV\",\n \"risk\": \"Limited\",\n \"id\": 9\n },\n {\n \"text\": \"Controller - VRF\",\n \"risk\": \"Limited\",\n \"id\": 10\n },\n {\n \"text\": \"Controller - Multiple\",\n \"risk\": \"High\",\n \"id\": 11\n },\n {\n \"text\": \"Controller - Other\",\n \"risk\": \"High\",\n \"id\": 12\n },\n {\n \"text\": \"Controller - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 13\n },\n {\n \"text\": \"Controller - Blinds/Facades\",\n \"risk\": \"High\",\n \"id\": 14\n },\n {\n \"text\": \"Controller - Lifts/Elevators\",\n \"risk\": \"High\",\n \"id\": 15\n },\n {\n \"text\": \"Controller - UPS\",\n \"risk\": \"High\",\n \"id\": 16\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 17\n },\n {\n \"text\": \"Sensor - Vibration\",\n \"risk\": \"Limited\",\n \"id\": 18\n },\n {\n \"text\": \"Sensor - Humidity\",\n \"risk\": \"Limited\",\n \"id\": 19\n },\n {\n \"text\": \"Sensor - Water\",\n \"risk\": \"Limited\",\n \"id\": 20\n },\n {\n \"text\": \"Sensor - Occupancy\",\n \"risk\": \"High\",\n \"id\": 21\n },\n {\n \"text\": \"Sensor - Volume\",\n \"risk\": \"Limited\",\n \"id\": 22\n },\n {\n \"text\": \"Sensor - Weight\",\n \"risk\": \"Limited\",\n \"id\": 23\n },\n {\n \"text\": \"Sensor - Weather\",\n \"risk\": \"Limited\",\n \"id\": 24\n },\n {\n \"text\": \"Sensor - Steam\",\n \"risk\": \"High\",\n \"id\": 25\n },\n {\n \"text\": \"Sensor - Air Flow\",\n \"risk\": \"Limited\",\n \"id\": 26\n },\n {\n \"text\": \"Sensor - Lighting\",\n \"risk\": \"Limited\",\n \"id\": 27\n },\n {\n \"text\": \"Sensor - Other\",\n \"risk\": \"High\",\n \"id\": 28\n },\n {\n \"text\": \"Sensor - Air Quality\",\n \"risk\": \"Limited\",\n \"id\": 29\n },\n {\n \"text\": \"Monitoring - Fire System\",\n \"risk\": \"Limited\",\n \"id\": 30\n },\n {\n \"text\": \"Monitoring - Emergency Lighting\",\n \"risk\": \"Limited\",\n \"id\": 31\n },\n {\n \"text\": \"Monitoring - Other\",\n \"risk\": \"High\",\n \"id\": 32\n },\n {\n \"text\": \"Monitoring - UPS\",\n \"risk\": \"Limited\",\n \"id\": 33\n },\n {\n \"text\": \"Meter - Water\",\n \"risk\": \"Limited\",\n \"id\": 34\n },\n {\n \"text\": \"Meter - Gas\",\n \"risk\": \"Limited\",\n \"id\": 35\n },\n {\n \"text\": \"Meter - Electricity\",\n \"risk\": \"Limited\",\n \"id\": 36\n },\n {\n \"text\": \"Meter - Other\",\n \"risk\": \"High\",\n \"id\": 37\n },\n {\n \"text\": \"Other\",\n \"risk\": \"High\",\n \"id\": 38\n },\n {\n \"text\": \"Data - Storage\",\n \"risk\": \"High\",\n \"id\": 39\n },\n {\n \"text\": \"Data - Processing\",\n \"risk\": \"High\",\n \"id\": 40\n },\n {\n \"text\": \"Tablet\",\n \"risk\": \"High\",\n \"id\": 41\n }\n ]\n },\n {\n \"id\": 2,\n \"question\": \"Please select the technology this device falls into\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Hardware - Access Control\"\n },\n {\n \"id\": 2,\n \"text\": \"Hardware - Air quality\"\n },\n {\n \"id\": 3,\n \"text\": \"Hardware - Asset location tracking\"\n },\n {\n \"id\": 4,\n \"text\": \"Hardware - Audio Visual\"\n },\n {\n \"id\": 5,\n \"text\": \"Hardware - Blinds/Facade\"\n },\n {\n \"id\": 6,\n \"text\": \"Hardware - Cameras\"\n },\n {\n \"id\": 7,\n \"text\": \"Hardware - Catering\"\n },\n {\n \"id\": 8,\n \"text\": \"Hardware - Data Ingestion/Managment\"\n },\n {\n \"id\": 9,\n \"text\": \"Hardware - EV Charging\"\n },\n {\n \"id\": 10,\n \"text\": \"Hardware - Fitness\"\n },\n {\n \"id\": 11,\n \"text\": \"Hardware - HVAC\"\n },\n {\n \"id\": 12,\n \"text\": \"Hardware - Irrigation\"\n },\n {\n \"id\": 13,\n \"text\": \"Hardware - Leak Detection\"\n },\n {\n \"id\": 14,\n \"text\": \"Hardware - Lifts/Elevators\"\n },\n {\n \"id\": 15,\n \"text\": \"Hardware - Lighting\"\n },\n {\n \"id\": 16,\n \"text\": \"Hardware - Metering\"\n },\n {\n \"id\": 17,\n \"text\": \"Hardware - Monitoring\"\n },\n {\n \"id\": 18,\n \"text\": \"Hardware - Occupancy\"\n },\n {\n \"id\": 19,\n \"text\": \"Hardware - System Integration\"\n },\n {\n \"id\": 20,\n \"text\": \"Hardware - Time Management\"\n },\n {\n \"id\": 21,\n \"text\": \"Hardware - UPS\"\n },\n {\n \"id\": 22,\n \"text\": \"Hardware - Waste Management\"\n },\n {\n \"id\": 23,\n \"text\": \"Building Automation\"\n },\n {\n \"id\": 24,\n \"text\": \"Other\"\n }\n ]\n },\n {\n \"id\": 3,\n \"question\": \"Does your device process any sensitive information? \",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"id\": 1,\n \"text\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"id\": 2,\n \"text\": \"No\",\n \"risk\": \"High\"\n },\n {\n \"id\": 3,\n \"text\": \"I don't know\",\n \"risk\": \"High\"\n }\n ]\n }\n ]\n },\n {\n \"step\": 2,\n \"title\": \"Tell us more about your device\",\n \"description\": \"Before your device can go through testing, tell us more about your device and its functionality. It is important that we fully understand your device before a thorough assessment can be made.\",\n \"questions\": [\n {\n \"id\": 1,\n \"question\": \"Can all non-essential services be disabled on your device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 2,\n \"question\": \"Is there a second IP port on the device?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n }\n ]\n },\n {\n \"id\": 3,\n \"question\": \"Can the second IP port on your device be disabled?\",\n \"validation\": {\n \"required\": true\n },\n \"type\": \"select\",\n \"options\": [\n {\n \"text\": \"Yes\",\n \"id\": 1\n },\n {\n \"text\": \"No\",\n \"id\": 2\n },\n {\n \"text\": \"N/A\",\n \"id\": 3\n }\n ]\n }\n ]\n }\n]" + } + ] + }, + { + "name": "Get System Modules", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/modules", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "modules" + ] + }, + "description": "Get a list of all root CA certificates that have been loaded into Testrun" + }, + "response": [ + { + "name": "Get System Modules (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/modules", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "modules" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n \"Protocol\",\n \"Services\",\n \"Connection\",\n \"TLS\",\n \"NTP\",\n \"DNS\"\n]" + } + ] + }, + { + "name": "Get System Testpacks", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/testpacks", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "testpacks" + ] + }, + "description": "Obtain a list of applicable and available network adapters for use in testing" + }, + "response": [ + { + "name": "System Testpacks (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "localhost:8000/system/testpacks", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "testpacks" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n \"Pilot Assessment\",\n \"Device Qualification\"\n]" + } + ] + }, + { + "name": "List Certificates", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + }, + "description": "Get a list of all root CA certificates that have been loaded into Testrun" + }, + "response": [ + { + "name": "List Certificates (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[\n {\n \"name\": \"GTS Root R1\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2036-06-22T00:00:00+00:00\",\n \"filename\": \"crt.pem\"\n },\n {\n \"name\": \"WR2\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2029-02-20T14:00:00+00:00\",\n \"filename\": \"WR2.pem\"\n }\n]" + }, + { + "name": "No Certificates (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "[]" + } + ] + }, + { + "name": "Upload Certificate", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": "NH1mNzqEQ/1c3.pem" + } + ] + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + }, + "description": "Upload a new root CA certificate into Testrun" + }, + "response": [ + { + "name": "Upload Certificate (201)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [ + "NH1mNzqEQ/1c3.pem" + ] + } + ] + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"name\": \"GTS Root R1\",\n \"status\": \"Valid\",\n \"organisation\": \"Google Trust Services LLC\",\n \"expires\": \"2036-06-22T00:00:00+00:00\",\n \"filename\": \"crt.pem\"\n}" + }, + { + "name": "Duplicate (409)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [ + "NH1mNzqEQ/1c3.pem" + ] + } + ] + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Conflict", + "code": 409, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A certificate with that file name already exists.\"\n}" + }, + { + "name": "Invalid Cert (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "formdata", + "formdata": [ + { + "key": "file", + "type": "file", + "src": [ + "postman-cloud:///1ef5fb1a-6342-44e0-b35f-700d496e0cc5" + ] + } + ] + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Failed to upload certificate. Is it in the correct format?\"\n}" + } + ] + }, + { + "name": "Delete Certificate", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"GTS CA 1C3\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + }, + "description": "Delete a root CA certificate" + }, + "response": [ + { + "name": "Delete Certificate (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"GTS Root R1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted the certificate\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing name\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Received a bad request\"\n}" + } + ] + }, + { + "name": "Get Profile Format", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + }, + "description": "Obtain the current format of the risk assessment questionnaire" + }, + "response": [ + { + "name": "Get Profile Format (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Desribe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\",\n \"required\": true\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device does not encrypt data during transmission\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is not required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\"\n }\n }\n]" + } + ] + }, + { + "name": "List Profiles", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Get a list of risk assessment profiles saved by the user" + }, + "response": [ + { + "name": "List Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]" + }, + { + "name": "No Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n \n]" + } + ] + }, + { + "name": "Export Profile", + "request": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/{profile_name}", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "{profile_name}" + ] + }, + "description": "Get the PDF report for a specific device and timestamp" + }, + "response": [ + { + "name": "Get Profile No Device (200)", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/pdf", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "profile.pdf" + }, + { + "name": "Get Profile with Device (200))", + "originalRequest": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"mac_addr\": \"00:1e:42:35:73:c4\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "text", + "header": [ + { + "key": "Content-Type", + "value": "application/pdf", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "profile.pdf" + }, + { + "name": "Profile Not Found (404)", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/NonExistingProfile", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "NonExistingProfile" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Profile could not be found\"\n}" + }, + { + "name": "Device Not Found (404)", + "originalRequest": { + "method": "GET", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A device with that mac address could not be found\"\n}" + }, + { + "name": "Internal Server Error (500) Copy", + "originalRequest": { + "method": "POST", + "header": [], + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Error retrieving the profile PDF\"\n}" + } + ] + }, + { + "name": "Update Profile", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Testing\",\n \"status\": \"Valid\",\n \"questions\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"asdasd\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"N/A\"\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"answer\": [\n 3\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 6\n ]\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Create or update a risk assessment questionnaire response" + }, + "response": [ + { + "name": "Update Profile (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n{\n \"name\": \"New Profile\",\n \"rename\": \"Updated New Profile\",\n \"version\": \"2.0.1\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully updated that profile\"\n}" + }, + { + "name": "Create Profile (201)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "\n{\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}\n", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully created a new profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Not Implemented (501)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Implemented", + "code": 501, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Risk profiles are not available right now\"\n}" + } + ] + }, + { + "name": "Delete Profile", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Delete an existing risk assessment questionaire response" + }, + "response": [ + { + "name": "Delete Profile (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted that profile\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" + }, + { + "name": "Internal Serves Error (500)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"An error occurred whilst deleting that profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + } + ] + }, + { + "name": "http://localhost:8000/profile/Test", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "type": "text" + } + ], + "body": { + "mode": "raw", + "raw": "{\"mac_addr\": \"non_existing_mac_addr\"}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profile/Test", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profile", + "Test" + ] + }, + "description": "Delete a root CA certificate" + }, + "response": [ + { + "name": "Delete Certificate (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"GTS Root R1\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted the certificate\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing name\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A certificate with that name could not be found\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/system/config/certs", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "system", + "config", + "certs" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Received a bad request\"\n}" + } + ] + }, + { + "name": "Get Profile Format", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + }, + "description": "Obtain the current format of the risk assessment questionnaire" + }, + "response": [ + { + "name": "Get Profile Format (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles/format", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles", + "format" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"question\": \"How will this device be used at Google?\",\n \"description\": \"Desribe your use case. Add links to user journey diagrams and TDD if available.\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\",\n \"required\": true\n }\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"description\": \"A manufacturer or supplier is considered third party in this case\",\n \"type\": \"select\",\n \"options\": [\n \"Google\",\n \"Third Party\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"N/A\"\n ],\n \"default\": \"N/A\",\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Which of the following statements are true about this device?\",\n \"description\": \"This tells us about the types of data that are transmitted from this device and how the transmission is performed from a technical standpoint.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, confidential/sensitive business data, Intellectual Property and Trade Secrets, Critical Infrastructure and Identity Assets to a domain outside Alphabet's ownership\",\n \"Data transmission occurs across less-trusted networks (e.g. the internet).\",\n \"A failure in data transmission would likely have a substantial negative impact (https://www.rra.rocks/docs/standard_levels#levels-definitions)\",\n \"A confidentiality breach during transmission would have a substantial negative impact\",\n \"The device does not encrypt data during transmission\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Data Transmission\",\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"type\": \"select\",\n \"options\": [\n \"Yes\",\n \"No\",\n \"I don't know\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Remote Operation\",\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"description\": \"This tells us about how this device is managed remotely.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"PII/PHI, or confidential business data is accessible from the device without authentication\",\n \"Unrecoverable actions (e.g. disk wipe) can be performed remotely\",\n \"Authentication is not required for remote access\",\n \"The management interface is accessible from the public internet\",\n \"Static credentials are used for administration\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"category\": \"Operating Environment\",\n \"question\": \"Are any of the following statements true about this device?\",\n \"description\": \"This informs us about what other systems and processes this device is a part of.\",\n \"type\": \"select-multiple\",\n \"options\": [\n \"The device monitors an environment for active risks to human life.\",\n \"The device is used to convey people, or critical property.\",\n \"The device controls robotics in human-accessible spaces.\",\n \"The device controls physical access systems.\",\n \"The device is involved in processes required by regulations, or compliance. (ex. privacy, security, safety regulations)\",\n \"The device's failure would cause faults in other high-criticality processes.\",\n \"None of the above\"\n ],\n \"validation\": {\n \"required\": true\n }\n },\n {\n \"question\": \"Comments\",\n \"description\": \"Anything else to share?\",\n \"type\": \"text-long\",\n \"validation\": {\n \"max\": \"512\"\n }\n }\n]" + } + ] + }, + { + "name": "List Profiles", + "request": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Get a list of risk assessment profiles saved by the user" + }, + "response": [ + { + "name": "List Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]" + }, + { + "name": "No Profiles (200)", + "originalRequest": { + "method": "GET", + "header": [], + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "" + } + ], + "cookie": [], + "body": "[\n \n]" + } + ] + }, + { + "name": "Update Profile", + "request": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"Testing\",\n \"status\": \"Valid\",\n \"questions\": [\n {\n \"question\": \"What type of device is this?\",\n \"answer\": \"IoT Gateway\"\n },\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"asdasd\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"N/A\"\n },\n {\n \"question\": \"Are any of the following statements true about your device?\",\n \"answer\": [\n 3\n ]\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 5\n ]\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 6\n ]\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Create or update a risk assessment questionnaire response" + }, + "response": [ + { + "name": "Update Profile (200)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully updated that profile\"\n}" + }, + { + "name": "Create Profile (201)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Created", + "code": 201, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully created a new profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + }, + { + "name": "Not Implemented (501)", + "originalRequest": { + "method": "POST", + "header": [], + "body": { + "mode": "raw", + "raw": "[\n {\n \"name\": \"New Profile\",\n \"version\": \"2.0\",\n \"created\": \"2024-10-08\",\n \"status\": \"Valid\",\n \"risk\": \"High\",\n \"questions\": [\n {\n \"question\": \"How will this device be used at Google?\",\n \"answer\": \"Yes\"\n },\n {\n \"question\": \"Is this device going to be managed by Google or a third party?\",\n \"answer\": \"Google\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Will the third-party device administrator be able to grant access to authorized Google personnel upon request?\",\n \"answer\": \"No\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Which of the following statements are true about this device?\",\n \"answer\": [\n 0,\n 1,\n 2\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Does the network protocol assure server-to-client identity verification?\",\n \"answer\": \"Yes\",\n \"risk\": \"Limited\"\n },\n {\n \"question\": \"Click the statements that best describe the characteristics of this device.\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Are any of the following statements true about this device?\",\n \"answer\": [\n 0\n ],\n \"risk\": \"High\"\n },\n {\n \"question\": \"Comments\",\n \"answer\": \"\"\n }\n ]\n }\n]", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Implemented", + "code": 501, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Risk profiles are not available right now\"\n}" + } + ] + }, + { + "name": "Delete Profile", + "request": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + }, + "description": "Delete an existing risk assessment questionaire response" + }, + "response": [ + { + "name": "Delete Profile (200)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"New Profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "OK", + "code": 200, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"success\": \"Successfully deleted that profile\"\n}" + }, + { + "name": "Not Found (404)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Not Found", + "code": 404, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"A profile with that name could not be found\"\n}" + }, + { + "name": "Internal Serves Error (500)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n \"name\": \"non-existing profile\"\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Internal Server Error", + "code": 500, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"An error occurred whilst deleting that profile\"\n}" + }, + { + "name": "Invalid JSON (400)", + "originalRequest": { + "method": "DELETE", + "header": [], + "body": { + "mode": "raw", + "raw": "{\n\n}", + "options": { + "raw": { + "language": "json" + } + } + }, + "url": { + "raw": "http://localhost:8000/profiles", + "protocol": "http", + "host": [ + "localhost" + ], + "port": "8000", + "path": [ + "profiles" + ] + } + }, + "status": "Bad Request", + "code": 400, + "_postman_previewlanguage": "json", + "header": [ + { + "key": "Content-Type", + "value": "application/json", + "name": "Content-Type", + "description": "", + "type": "text" + } + ], + "cookie": [], + "body": "{\n \"error\": \"Invalid request received\"\n}" + } + ] + } + ] } \ No newline at end of file diff --git a/framework/python/src/api/api.py b/framework/python/src/api/api.py index 56f93c781..1c5a2a01f 100644 --- a/framework/python/src/api/api.py +++ b/framework/python/src/api/api.py @@ -16,7 +16,6 @@ from fastapi.responses import FileResponse from fastapi.middleware.cors import CORSMiddleware import asyncio -from datetime import datetime import json from json import JSONDecodeError import os @@ -43,6 +42,7 @@ DEVICE_ADDITIONAL_INFO_KEY = "additional_info" DEVICES_PATH = "local/devices" +REPORTS_PATH = "local/reports" PROFILES_PATH = "local/risk_profiles" RESOURCES_PATH = "resources" @@ -96,12 +96,12 @@ def __init__(self, testrun): # Report endpoints self._router.add_api_route("/reports", self.get_reports) - self._router.add_api_route("/report", + self._router.add_api_route("/report/{report_name}", self.delete_report, methods=["DELETE"]) - self._router.add_api_route("/report/{device_name}/{timestamp}", + self._router.add_api_route("/report/{report_name}", self.get_report) - self._router.add_api_route("/export/{device_name}/{timestamp}", + self._router.add_api_route("/export/{report_name}", self.get_results, methods=["POST"]) @@ -441,66 +441,34 @@ async def get_version(self, response: Response): LOGGER.debug(e) return json_response - async def get_reports(self, request: Request): + async def get_reports(self): LOGGER.debug("Received reports list request") # Resolve the server IP from the request so we # can fix the report URL reports = self._session.get_all_reports() for report in reports: - # report URL is currently hard coded as localhost so we can - # replace that to fix the IP dynamically from the requester - report["report"] = report["report"].replace( - "localhost", request.client.host) - report["export"] = report["report"].replace("report", "export") + del report["tests"] + report["device"] = { + "manufacturer": report["device"]["manufacturer"], + "model": report["device"]["model"], + "mac_addr": report["device"]["mac_addr"], + "firmware": report["device"]["firmware"], + "test_pack": report["device"]["test_pack"], + } + report["delete"] = report["report"] return reports - async def delete_report(self, request: Request, response: Response): + async def delete_report(self, response: Response, report_name: str): - body_raw = (await request.body()).decode("UTF-8") - - if len(body_raw) == 0: - response.status_code = 400 - return self._generate_msg(False, "Invalid request received, missing body") - - try: - body_json = json.loads(body_raw) - except JSONDecodeError as e: - LOGGER.error("An error occurred whilst decoding JSON") - LOGGER.debug(e) - response.status_code = status.HTTP_400_BAD_REQUEST - return self._generate_msg(False, "Invalid request received") - - if "mac_addr" not in body_json or "timestamp" not in body_json: - response.status_code = 400 - return self._generate_msg(False, "Missing mac address or timestamp") - - mac_addr = body_json.get("mac_addr").lower() - timestamp = body_json.get("timestamp") - - try: - parsed_timestamp = datetime.strptime(timestamp, "%Y-%m-%d %H:%M:%S") - timestamp_formatted = parsed_timestamp.strftime("%Y-%m-%dT%H:%M:%S") - - except ValueError: - response.status_code = 400 - return self._generate_msg(False, "Incorrect timestamp format") - - # Get device from MAC address - device = self._session.get_device(mac_addr) - - if device is None: - response.status_code = 404 - return self._generate_msg(False, "Could not find device") - - # Assign the reports folder path from testrun - reports_folder = self._testrun.get_reports_folder(device) - - # Check if reports folder exists - if not os.path.exists(reports_folder): + device_with_report = self._session.get_report(report_name) + if device_with_report.device is None or device_with_report.report is None: + LOGGER.info("Report could not be found, returning 404") response.status_code = 404 - return self._generate_msg(False, "Report not found") + return self._generate_msg(False, "Report not found from list") + device = device_with_report.device + report = device_with_report.report - if self._testrun.delete_report(device, timestamp_formatted): + if self._testrun.delete_report(device, report): return self._generate_msg(True, "Deleted report") response.status_code = 500 @@ -702,32 +670,21 @@ async def edit_device(self, request: Request, response: Response): response.status_code = status.HTTP_400_BAD_REQUEST return self._generate_msg(False, "Invalid JSON received") - async def get_report(self, response: Response, device_name, timestamp): - device = self._session.get_device_by_name(device_name) - - # If the device not found - if device is None: - LOGGER.info("Device not found, returning 404") + async def get_report(self, response: Response, report_name): + """Serve report pdf file for a given report name""" + device_with_report = self._session.get_report(report_name) + if device_with_report.device is None or device_with_report.report is None: + LOGGER.info("Report could not be found, returning 404") response.status_code = 404 - return self._generate_msg(False, "Device not found") + return self._generate_msg(False, "Report not found from list") + device = device_with_report.device + report = device_with_report.report # Regenerate the pdf if the device profile has been updated - self._get_testrun().get_test_orc().regenerate_pdf(device, timestamp) - - # 1.3 file path - file_path = os.path.join( - DEVICES_PATH, - device_name, - "reports", - timestamp,"test", - device.mac_addr.replace(":",""), - "report.pdf") - if not os.path.isfile(file_path): - # pre 1.3 file path - file_path = os.path.join(DEVICES_PATH, device_name, "reports", timestamp, - "report.pdf") - - LOGGER.debug(f"Received get report request for {device_name} / {timestamp}") + test_orc = self._get_testrun().get_test_orc() + test_path = test_orc.regenerate_pdf(device, report) + file_path = os.path.join(test_path, "report.pdf") + LOGGER.debug(f"Received get report request for {device.model}") if os.path.isfile(file_path): return FileResponse(file_path) else: @@ -735,10 +692,13 @@ async def get_report(self, response: Response, device_name, timestamp): response.status_code = 404 return self._generate_msg(False, "Report could not be found") - async def get_results(self, request: Request, response: Response, device_name, - timestamp): - LOGGER.debug("Received get results " + - f"request for {device_name} / {timestamp}") + async def get_results( + self, + request: Request, + response: Response, + report_name: str + ): + LOGGER.debug(f"Received get results request for {report_name}") profile = None @@ -761,32 +721,16 @@ async def get_results(self, request: Request, response: Response, device_name, pass # Check if device exists - device = self.get_session().get_device_by_name(device_name) - if device is None: - response.status_code = status.HTTP_404_NOT_FOUND - return self._generate_msg(False, - "A device with that name could not be found") - - # Check if report exists (1.3 file path) - report_file_path = os.path.join( - DEVICES_PATH, - device_name, - "reports", - timestamp,"test", - device.mac_addr.replace(":","")) - - if not os.path.isdir(report_file_path): - # pre 1.3 file path - report_file_path = os.path.join(DEVICES_PATH, device_name, "reports", - timestamp) - - if not os.path.isdir(report_file_path): + device_with_report = self._session.get_report(report_name) + if device_with_report.device is None or device_with_report.report is None: LOGGER.info("Report could not be found, returning 404") response.status_code = 404 - return self._generate_msg(False, "Report could not be found") + return self._generate_msg(False, "Report not found from list") + device = device_with_report.device + report = device_with_report.report zip_file_path = self._get_testrun().get_test_orc().zip_results( - device, timestamp, profile) + device, report, profile) if zip_file_path is None: response.status_code = status.HTTP_500_INTERNAL_SERVER_ERROR diff --git a/framework/python/src/common/device.py b/framework/python/src/common/device.py index 0b2ea3162..f5082e828 100644 --- a/framework/python/src/common/device.py +++ b/framework/python/src/common/device.py @@ -14,11 +14,16 @@ """Track device object information.""" +import json +import os from typing import List, Dict from dataclasses import dataclass, field from common.testreport import TestReport from datetime import datetime +_LOCAL_DEVICES_DIR = 'local/devices' +_DEVICE_CONFIG_FILE = 'device_config.json' + @dataclass class Device(): """Represents a physical device and it's configuration.""" @@ -47,17 +52,36 @@ class Device(): def add_report(self, report): self.reports.append(report) + def get_report_by_folder_name(self, folder_name: str) -> TestReport | None: + for report in self.reports: + report_folder_name = report.get_folder_name() + if report_folder_name == folder_name: + return report + if report_folder_name is None or report_folder_name == '': + if report.get_report_url().split('/')[-1] == folder_name: + report.set_report_url(folder_name) + return report + return None + def get_reports(self): return self.reports + def sort_reports(self): + self.reports.sort(key=lambda r: r.get_started() or datetime.min) + def clear_reports(self): self.reports = [] - def remove_report(self, timestamp: datetime): + def remove_report(self, report: TestReport): + if report in self.reports: + self.reports.remove(report) + report.delete_folder() + self.export_config_json() + + def remove_reports(self): for report in self.reports: - if report.get_started().strftime('%Y-%m-%dT%H:%M:%S') == timestamp: - self.reports.remove(report) - return + report.delete_folder() + self.clear_reports() def to_dict(self): """Returns the device as a python dictionary. This is used for the @@ -78,6 +102,8 @@ def to_dict(self): device_json['firmware'] = self.firmware device_json['test_modules'] = self.test_modules + device_json['reports'] = [ + report.to_json() for report in self.reports] if self.reports else [] return device_json def to_config_json(self): @@ -94,9 +120,23 @@ def to_config_json(self): device_json['additional_info'] = self.additional_info device_json['created_at'] = self.created_at.isoformat() device_json['modified_at'] = self.modified_at.isoformat() + device_json['reports'] = [ + report.to_json() for report in self.reports] if self.reports else [] return device_json + def export_config_json(self): + """Exports the device config as a json file to the specified path.""" + # Locate parent directory + current_dir = os.path.dirname(os.path.realpath(__file__)) + root_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))) + config_file_path = os.path.join(root_dir, _LOCAL_DEVICES_DIR, + self.device_folder, _DEVICE_CONFIG_FILE) + + with open(config_file_path, 'w+', encoding='utf-8') as config_file: + config_file.writelines(json.dumps(self.to_config_json(), indent=4)) + def __post_init__(self): # Store initial values after creation for f in self.__dataclass_fields__: @@ -109,3 +149,9 @@ def __setattr__(self, name: str, value: any) -> None: # Update the last_updated timestamp super().__setattr__('modified_at', datetime.now()) super().__setattr__(name, value) + + +@dataclass +class DeviceWithReport(): + device: Device | None = None + report: TestReport | None = None diff --git a/framework/python/src/common/mqtt.py b/framework/python/src/common/mqtt.py index b98b4ab1b..f71f2f584 100644 --- a/framework/python/src/common/mqtt.py +++ b/framework/python/src/common/mqtt.py @@ -17,6 +17,13 @@ import typing as t import paho.mqtt.client as mqtt_client from common import logger +from enum import Enum + +class MQTTTopic(str, Enum): + INFO = "info" + INTERNET_CONNECTION_TOPIC = "events/internet" + NETWORK_ADAPTERS_TOPIC = "events/adapter" + STATUS_TOPIC = "status" LOGGER = logger.get_logger("mqtt") WEBSOCKETS_HOST = "localhost" @@ -47,7 +54,7 @@ def disconnect(self): LOGGER.debug("Disconnecting from broker") self._client.disconnect() - def send_message(self, topic: str, message: t.Union[str, dict]) -> None: + def send_message(self, topic: MQTTTopic, message: t.Union[str, dict]) -> None: """Send message to specific topic Args: @@ -57,4 +64,4 @@ def send_message(self, topic: str, message: t.Union[str, dict]) -> None: self._connect() if isinstance(message, dict): message = json.dumps(message) - self._client.publish(topic, str(message)) + self._client.publish(topic.value, str(message)) diff --git a/framework/python/src/common/testreport.py b/framework/python/src/common/testreport.py index 8f2c998c5..a213fd7c4 100644 --- a/framework/python/src/common/testreport.py +++ b/framework/python/src/common/testreport.py @@ -14,6 +14,7 @@ """Store previous Testrun information.""" from datetime import datetime +import shutil from weasyprint import HTML from io import BytesIO from common import util, logger @@ -39,6 +40,11 @@ RESULTS_SPACE_FIRST_PAGE = 440 RESULTS_SPACE = 800 +_REPORTS_FOLDER = 'local/reports' +_CURRENT_DIR = os.path.dirname(os.path.abspath(__file__)) +_ROOT_DIR = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(_CURRENT_DIR)))) + LOGGER = logger.get_logger('REPORT') @@ -76,10 +82,16 @@ def __init__(self, self._report_url = '' self._export_url = '' self._cur_page = 0 + self._folder_name = '' def update_device_profile(self, additional_info): self._device['device_profile'] = additional_info + def update_device_info(self, device): + self._device['manufacturer'] = device.manufacturer + self._device['model'] = device.model + self._device['device_profile'] = device.additional_info + def add_module_reports(self, module_reports): self._module_reports = module_reports @@ -108,8 +120,9 @@ def get_duration(self): def add_test(self, test): self._results.append(test) - def set_report_url(self, url): - self._report_url = url + def set_report_url(self, folder_name: str): + self._folder_name = folder_name + self._report_url = f'/report/{folder_name}' def get_report_url(self): return self._report_url @@ -117,6 +130,24 @@ def get_report_url(self): def get_export_url(self): return self._export_url + def set_export_url(self, folder_name: str): + self._export_url = f'/export/{folder_name}' + + def get_folder_name(self) -> str: + return self._folder_name + + def delete_folder(self): + # Delete report from disk + if self._folder_name: + report_path = os.path.join(_ROOT_DIR, _REPORTS_FOLDER, self._folder_name) + if os.path.exists(report_path): + try: + shutil.rmtree(report_path) + except FileNotFoundError: + LOGGER.error( + f'Report folder not found for deletion: {report_path}' + ) + def set_mac_addr(self, mac_addr): self._mac_addr = mac_addr @@ -165,6 +196,8 @@ def to_json(self): report_json['tests'] = {'total': self._total_tests, 'results': test_results} report_json['report'] = self._report_url + report_json['export'] = self._export_url + report_json['folder_name'] = self._folder_name return report_json def from_json(self, json_file): @@ -209,6 +242,8 @@ def from_json(self, json_file): self._report_url = json_file['report'] if 'export' in json_file: self._export_url = json_file['export'] + if 'folder_name' in json_file: + self._folder_name = json_file['folder_name'] self._total_tests = json_file['tests']['total'] @@ -234,6 +269,10 @@ def from_json(self, json_file): self.add_test(test_case) + def to_json_updated(self, device): + self.update_device_info(device) + return self.to_json() + # Create a pdf file in memory and return the bytes def to_pdf(self): # Resolve the data as html first @@ -244,6 +283,12 @@ def to_pdf(self): HTML(string=report_html).write_pdf(pdf_bytes) return pdf_bytes + def to_pdf_from_html(self, html_content): + # Convert HTML to PDF in memory using weasyprint + pdf_bytes = BytesIO() + HTML(string=html_content).write_pdf(pdf_bytes) + return pdf_bytes + def to_html(self): # Obtain test pack diff --git a/framework/python/src/core/session.py b/framework/python/src/core/session.py index be1ca1ca1..d873a94a4 100644 --- a/framework/python/src/core/session.py +++ b/framework/python/src/core/session.py @@ -21,6 +21,7 @@ from common import util, logger, mqtt from common.risk_profile import RiskProfile from common.statuses import TestrunStatus, TestResult, TestrunResult +from common.device import Device, DeviceWithReport from net_orc.ip_control import IPControl # Certificate dependencies @@ -42,7 +43,6 @@ ALLOW_DISCONNECT_KEY='allow_disconnect' CERTS_PATH = 'local/root_certs' CONFIG_FILE_PATH = 'local/system.json' -STATUS_TOPIC = 'status' MAKE_CONTROL_DIR = 'make/DEBIAN/control' @@ -66,7 +66,7 @@ def wrapper(self, *args, **kwargs): if self.get_status() != TestrunStatus.IDLE and not self.pause_message: self.get_mqtt_client().send_message( - STATUS_TOPIC, + mqtt.MQTTTopic.STATUS_TOPIC, jsonable_encoder(self.to_json()) ) if self.get_status() in STATUSES_COMPLETE: @@ -384,9 +384,25 @@ def get_device_by_make_and_model(self, make, model): if device.manufacturer == make and device.model == model: return device + def get_device_by_mac_addr(self, mac_addr_simmplified: str) -> Device | None: + for device in self._device_repository: + if (device.mac_addr is not None + and device.mac_addr.replace(':', '') == mac_addr_simmplified): + return device + def get_device_repository(self): return self._device_repository + def get_report(self, folder_name: str) -> DeviceWithReport: + device_with_report = DeviceWithReport() + for device in self._device_repository: + device_reports = device.get_reports() + for report in device_reports: + if report.get_folder_name() == folder_name: + device_with_report.device = device + device_with_report.report = report + return device_with_report + def add_device(self, device): self._device_repository.append(device) @@ -518,8 +534,12 @@ def get_all_reports(self): for device in self.get_device_repository(): device_reports = device.get_reports() - for device_report in device_reports: - reports.append(device_report.to_json()) + reports.extend( + [ + device_report.to_json_updated(device) + for device_report in device_reports + ] + ) return sorted(reports, key=lambda report: report['started'], reverse=True) def add_total_tests(self, no_tests): @@ -997,13 +1017,20 @@ def load_certs(self): if now > cert.not_valid_after_utc: status = 'Expired' + # Determine if certificate is root or intermediate. + if cert.issuer == cert.subject: + cert_type = 'root' + else: + cert_type = 'intermediate' + # Craft python dictionary with values cert_obj = { 'name': common_name, 'status': status, 'organisation': issuer, 'expires': cert.not_valid_after_utc, - 'filename': cert_file + 'filename': cert_file, + 'type': cert_type, } # Add certificate to list diff --git a/framework/python/src/core/tasks.py b/framework/python/src/core/tasks.py index 5da0b40c9..ce40bf310 100644 --- a/framework/python/src/core/tasks.py +++ b/framework/python/src/core/tasks.py @@ -20,14 +20,12 @@ from apscheduler.schedulers.asyncio import AsyncIOScheduler from fastapi import FastAPI -from common import logger +from common import logger, mqtt # Check adapters period seconds # Check adapters period seconds CHECK_NETWORK_ADAPTERS_PERIOD = 5 CHECK_INTERNET_PERIOD = 2 -INTERNET_CONNECTION_TOPIC = 'events/internet' -NETWORK_ADAPTERS_TOPIC = 'events/adapter' LOGGER = logger.get_logger('tasks') @@ -49,7 +47,7 @@ def __init__( func=self._testrun.get_net_orc().network_adapters_checker, kwargs={ 'mqtt_client': self._mqtt_client, - 'topic': NETWORK_ADAPTERS_TOPIC + 'topic': mqtt.MQTTTopic.NETWORK_ADAPTERS_TOPIC }, trigger='interval', seconds=CHECK_NETWORK_ADAPTERS_PERIOD, @@ -60,7 +58,7 @@ def __init__( func=self._testrun.get_net_orc().internet_conn_checker, kwargs={ 'mqtt_client': self._mqtt_client, - 'topic': INTERNET_CONNECTION_TOPIC + 'topic': mqtt.MQTTTopic.INTERNET_CONNECTION_TOPIC }, trigger='interval', seconds=CHECK_INTERNET_PERIOD, diff --git a/framework/python/src/core/testrun.py b/framework/python/src/core/testrun.py index 069552320..c7af703e1 100644 --- a/framework/python/src/core/testrun.py +++ b/framework/python/src/core/testrun.py @@ -1,584 +1,638 @@ -# Copyright 2023 Google LLC -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -"""The overall control of the Testrun application. -This file provides the integration between all of the -Testrun components, such as net_orc, test_orc and test_ui. -""" -import docker -import json -import os -import shutil -import signal -import sys -import time -import docker.errors - -from common import logger, util, mqtt -from common.device import Device -from common.testreport import TestReport -from common.statuses import TestrunStatus -from session import TestrunSession -from api.api import Api -from net_orc.listener import NetworkEvent -from net_orc import network_orchestrator as net_orc -from test_orc import test_orchestrator as test_orc - -LOGGER = logger.get_logger('testrun') - -DEFAULT_CONFIG_FILE = 'local/system.json' -EXAMPLE_CONFIG_FILE = 'local/system.json.example' - -LOCAL_DEVICES_DIR = 'local/devices' -RESOURCE_DEVICES_DIR = 'resources/devices' - -DEVICE_CONFIG = 'device_config.json' -DEVICE_MANUFACTURER = 'manufacturer' -DEVICE_MODEL = 'model' -DEVICE_MAC_ADDR = 'mac_addr' -DEVICE_TEST_MODULES = 'test_modules' -DEVICE_TYPE_KEY = 'type' -DEVICE_TECHNOLOGY_KEY = 'technology' -DEVICE_TEST_PACK_KEY = 'test_pack' -DEVICE_ADDITIONAL_INFO_KEY = 'additional_info' - -MAX_DEVICE_REPORTS_KEY = 'max_device_reports' - - -class Testrun: # pylint: disable=too-few-public-methods - """Testrun controller. - - Creates an instance of the network orchestrator, test - orchestrator and user interface. - """ - - def __init__(self, - config_file, - validate=False, - net_only=False, - single_intf=False, - no_ui=False, - target_mac=None, - firmware=None): - - # Locate parent directory - current_dir = os.path.dirname(os.path.realpath(__file__)) - - # Locate the test-run root directory, 4 levels, - # src->python->framework->test-run - self._root_dir = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))) - - # Determine config file - if config_file is None: - self._config_file = self._get_config_abs(DEFAULT_CONFIG_FILE) - else: - self._config_file = self._get_config_abs(config_file) - - self._net_only = net_only - self._single_intf = single_intf - # Network only option only works if UI is also - # disbled so need to set no_ui if net_only is selected - self._no_ui = no_ui or net_only - - # Catch any exit signals - self._register_exits() - - # Create session - self._session = TestrunSession(root_dir=self._root_dir) - - # Register runtime parameters - if single_intf: - self._session.add_runtime_param('single_intf') - if net_only: - self._session.add_runtime_param('net_only') - if validate: - self._session.add_runtime_param('validate') - - self._net_orc = net_orc.NetworkOrchestrator(session=self._session) - self._test_orc = test_orc.TestOrchestrator(self._session, self._net_orc) - - # Load device repository - self.load_all_devices() - - # If no_ui selected and not network only mode, - # load the target device into the session - if self._no_ui and not net_only: - target_device = self._session.get_device(target_mac) - if target_device is not None: - target_device.firmware = firmware - self._session.set_target_device(target_device) - else: - print( - f'Target device specified does not exist in device registry: ' - f'{target_mac}', - file=sys.stderr) - sys.exit(1) - - # Load test modules - self._test_orc.start() - - # Start websockets server - self.start_ws() - - # Init MQTT client - self._mqtt_client = mqtt.MQTT() - - if self._no_ui: - - # Check Testrun is able to start - if self.get_net_orc().check_config() is False: - return - - # Any additional checks that need to be performed go here - - self.start() - - else: - - # Start UI container - self.start_ui() - - self._api = Api(self) - self._api.start() - - # Hold until API ends - while True: - time.sleep(1) - - def get_root_dir(self): - return self._root_dir - - def get_version(self): - return self.get_session().get_version() - - def load_all_devices(self): - self._session.clear_device_repository() - self._load_devices(device_dir=LOCAL_DEVICES_DIR) - - # Temporarily removing loading of template device - # configs (feature not required yet) - # self._load_devices(device_dir=RESOURCE_DEVICES_DIR) - return self.get_session().get_device_repository() - - def _load_devices(self, device_dir): - LOGGER.debug('Loading devices from ' + device_dir) - - util.run_command(f'chown -R {util.get_host_user()} {device_dir}') - - for device_folder in os.listdir(device_dir): - - device_config_file_path = os.path.join(device_dir, device_folder, - DEVICE_CONFIG) - - # Check if device config file exists before loading - if not os.path.exists(device_config_file_path): - LOGGER.error('Device configuration file missing ' + - f'for device {device_folder}') - continue - - # Open device config file - with open(device_config_file_path, - encoding='utf-8') as device_config_file: - - try: - device_config_json = json.load(device_config_file) - except json.decoder.JSONDecodeError as e: - LOGGER.error('Invalid JSON found in ' + - f'device configuration {device_config_file_path}') - LOGGER.debug(e) - continue - - device_manufacturer = device_config_json.get(DEVICE_MANUFACTURER) - device_model = device_config_json.get(DEVICE_MODEL) - mac_addr = device_config_json.get(DEVICE_MAC_ADDR) - test_modules = device_config_json.get(DEVICE_TEST_MODULES) - - # Load max device reports - max_device_reports = None - if 'max_device_reports' in device_config_json: - max_device_reports = device_config_json.get(MAX_DEVICE_REPORTS_KEY) - - folder_url = os.path.join(device_dir, device_folder) - - device = Device(folder_url=folder_url, - manufacturer=device_manufacturer, - model=device_model, - mac_addr=mac_addr, - test_modules=test_modules, - max_device_reports=max_device_reports, - device_folder=device_folder) - - # Load in the additional fields - if DEVICE_TYPE_KEY in device_config_json: - device.type = device_config_json.get(DEVICE_TYPE_KEY) - - if DEVICE_TECHNOLOGY_KEY in device_config_json: - device.technology = device_config_json.get(DEVICE_TECHNOLOGY_KEY) - - if DEVICE_TEST_PACK_KEY in device_config_json: - device.test_pack = device_config_json.get(DEVICE_TEST_PACK_KEY) - - if DEVICE_ADDITIONAL_INFO_KEY in device_config_json: - device.additional_info = device_config_json.get( - DEVICE_ADDITIONAL_INFO_KEY) - - if None in [device.type, device.technology, device.test_pack]: - LOGGER.warning( - 'Device is outdated and requires further configuration') - device.status = 'Invalid' - - self._load_test_reports(device) - - # Add device to device repository - self.get_session().add_device(device) - LOGGER.debug(f'Loaded device {device.manufacturer} ' + - f'{device.model} with MAC address {device.mac_addr}') - - def _load_test_reports(self, device): - - LOGGER.debug('Loading test reports for device ' + - f'{device.manufacturer} {device.model}') - - # Remove the existing reports in memory - device.clear_reports() - - # Locate reports folder - reports_folder = self.get_reports_folder(device) - - # Check if reports folder exists (device may have no reports) - if not os.path.exists(reports_folder): - return - - for report_folder in os.listdir(reports_folder): - # 1.3 file path - report_json_file_path = os.path.join(reports_folder, report_folder, - 'test', - device.mac_addr.replace(':', ''), - 'report.json') - - if not os.path.isfile(report_json_file_path): - # Revert to pre 1.3 file path - report_json_file_path = os.path.join(reports_folder, report_folder, - 'report.json') - - if not os.path.isfile(report_json_file_path): - # Revert to pre 1.3 file path - report_json_file_path = os.path.join(reports_folder, report_folder, - 'report.json') - - # Check if the report.json file exists - if not os.path.isfile(report_json_file_path): - # Some error may have occurred during this test run - continue - - with open(report_json_file_path, encoding='utf-8') as report_json_file: - report_json = json.load(report_json_file) - test_report = TestReport() - test_report.from_json(report_json) - test_report.set_mac_addr(device.mac_addr) - device.add_report(test_report) - - def get_reports_folder(self, device): - """Return the reports folder path for the device""" - return os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder, - 'reports') - - def delete_report(self, device: Device, timestamp): - LOGGER.debug(f'Deleting test report for device {device.model} ' + - f'at {timestamp}') - - # Locate reports folder - reports_folder = self.get_reports_folder(device) - - for report_folder in os.listdir(reports_folder): - if report_folder == timestamp: - shutil.rmtree(os.path.join(reports_folder, report_folder)) - device.remove_report(timestamp) - LOGGER.debug('Successfully deleted the report') - return True - - return False - - def create_device(self, device: Device): - - # Define the device folder location - device_folder_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, - device.device_folder) - - # Create the directory - os.makedirs(device_folder_path) - - config_file_path = os.path.join(device_folder_path, DEVICE_CONFIG) - - with open(config_file_path, 'w', encoding='utf-8') as config_file: - config_file.writelines(json.dumps(device.to_config_json(), indent=4)) - - # Ensure new folder has correct permissions - util.run_command(f"chown -R {util.get_host_user()} '{device_folder_path}'") - - # Add new device to the device repository - self._session.add_device(device) - - return device.to_config_json() - - def save_device(self, device: Device): - """Edit and save an existing device config.""" - - # Obtain the config file path - config_file_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, - device.device_folder, DEVICE_CONFIG) - - with open(config_file_path, 'w+', encoding='utf-8') as config_file: - config_file.writelines(json.dumps(device.to_config_json(), indent=4)) - - # Reload device reports - self._load_test_reports(device) - - return device.to_config_json() - - def delete_device(self, device: Device): - - # Obtain the config file path - device_folder = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, - device.device_folder) - - # Delete the device directory - shutil.rmtree(device_folder) - - # Remove the device from the current session device repository - self.get_session().remove_device(device) - - def start(self): - - self.get_session().start() - - self._start_network() - - self.get_net_orc().get_listener().register_callback( - self._device_discovered, [NetworkEvent.DEVICE_DISCOVERED]) - - if self._net_only: - LOGGER.info('Network only option configured, no tests will be run') - else: - self.get_net_orc().get_listener().register_callback( - self._device_stable, [NetworkEvent.DEVICE_STABLE]) - - self.get_net_orc().start_listener() - self.get_session().set_status(TestrunStatus.WAITING_FOR_DEVICE) - LOGGER.info('Waiting for devices on the network...') - - # Keep application running until stopped - while True: - time.sleep(5) - - async def stop(self): - - # First, change the status to stopping - self.get_session().stop() - - # First, change the status to stopping - self.get_session().stop() - - # Prevent discovering new devices whilst stopping - if self.get_net_orc().get_listener() is not None: - self.get_net_orc().get_listener().stop_listener() - - self._stop_tests() - - self.get_session().set_status(TestrunStatus.CANCELLED) - - # Disconnect before WS server stops to prevent error - self._mqtt_client.disconnect() - - self._stop_network(kill=True) - - def _register_exits(self): - signal.signal(signal.SIGINT, self._exit_handler) - signal.signal(signal.SIGTERM, self._exit_handler) - signal.signal(signal.SIGABRT, self._exit_handler) - signal.signal(signal.SIGQUIT, self._exit_handler) - - def shutdown(self): - LOGGER.info('Shutting down Testrun') - self.stop() - self._stop_ui() - self._stop_ws() - - def _exit_handler(self, signum, arg): # pylint: disable=unused-argument - LOGGER.debug('Exit signal received: ' + str(signum)) - if signum in (2, signal.SIGTERM): - LOGGER.info('Exit signal received.') - self.shutdown() - sys.exit(1) - - def _get_config_abs(self, config_file=None): - if config_file is None: - # If not defined, use relative pathing to local file - config_file = os.path.join(self._root_dir, self._config_file) - - # Expand the config file to absolute pathing - return os.path.abspath(config_file) - - def get_config_file(self): - return self._get_config_abs() - - def get_net_orc(self): - return self._net_orc - - def get_test_orc(self): - return self._test_orc - - def _start_network(self): - # Start the network orchestrator - if not self.get_net_orc().start(): - self.stop() - sys.exit(1) - - def _stop_network(self, kill=True): - self.get_net_orc().stop(kill) - - def _stop_tests(self): - self._test_orc.stop() - - def get_mqtt_client(self): - return self._mqtt_client - - def get_device(self, mac_addr): - """Returns a loaded device object from the device mac address.""" - for device in self.get_session().get_device_repository(): - if device.mac_addr == mac_addr: - return device - return None - - def _device_discovered(self, mac_addr): - - device = self.get_session().get_target_device() - - if device is not None: - if mac_addr != device.mac_addr: - LOGGER.info(f'Found device with mac addr: {mac_addr} but was ignored') - LOGGER.info(f'Expected device mac address is {device.mac_addr}') - # Ignore discovered device because it is not the target device - return - else: - device = self.get_device(mac_addr) - if device is None: - return - - self.get_session().set_target_device(device) - - LOGGER.info( - f'Discovered {device.manufacturer} {device.model} on the network. ' + - 'Waiting for device to obtain IP') - - def _device_stable(self, mac_addr): - - # Do not continue testing if Testrun has cancelled during monitor phase - if self.get_session().get_status() == TestrunStatus.CANCELLED: - self._stop_network() - return - - LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') - self._set_status(TestrunStatus.IN_PROGRESS) - - # Start testrun timer - self.get_session().start_timer() - - self._test_orc.run_test_modules() - - self._stop_network() - - def get_session(self): - return self._session - - def _set_status(self, status): - self.get_session().set_status(status) - - def start_ui(self): - - self._stop_ui() - - LOGGER.info('Starting UI') - - client = docker.from_env() - - try: - client.containers.run(image='testrun/ui', - auto_remove=True, - name='tr-ui', - hostname='testrun.io', - detach=True, - ports={'80': 8080}) - except docker.errors.ImageNotFound as ie: - LOGGER.error('An error occurred whilst starting the UI. ' + - 'Please investigate and try again.') - LOGGER.error(ie) - sys.exit(1) - - # TODO: Make port configurable - LOGGER.info('User interface is ready on http://localhost:8080') - - def _stop_ui(self): - LOGGER.info('Stopping user interface') - client = docker.from_env() - try: - container = client.containers.get('tr-ui') - if container is not None: - container.kill() - # If the container has been started without auto-remove flag remove it - try: - container.remove() - except docker.errors.APIError: - pass - except docker.errors.NotFound: - pass - - def start_ws(self): - - self._stop_ws() - - LOGGER.info('Starting WS server') - - client = docker.from_env() - - try: - client.containers.run(image='testrun/ws', - auto_remove=True, - name='tr-ws', - detach=True, - ports={ - '9001': 9001, - '1883': 1883 - }) - except docker.errors.ImageNotFound as ie: - LOGGER.error('An error occurred whilst starting the websockets server. ' + - 'Please investigate and try again.') - LOGGER.error(ie) - sys.exit(1) - - def _stop_ws(self): - LOGGER.info('Stopping websockets server') - client = docker.from_env() - try: - container = client.containers.get('tr-ws') - if container is not None: - container.kill() - # If the container has been started without auto-remove flag remove it - try: - container.remove() - except docker.errors.APIError: - pass - - except docker.errors.NotFound: - pass +# Copyright 2023 Google LLC +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""The overall control of the Testrun application. +This file provides the integration between all of the +Testrun components, such as net_orc, test_orc and test_ui. +""" +import docker +import json +import os +import shutil +import signal +import sys +import time +import docker.errors + +from common import logger, util, mqtt +from common.device import Device +from common.testreport import TestReport +from common.statuses import TestrunStatus +from session import TestrunSession +from api.api import Api +from net_orc.listener import NetworkEvent +from net_orc import network_orchestrator as net_orc +from test_orc import test_orchestrator as test_orc + +LOGGER = logger.get_logger('testrun') + +DEFAULT_CONFIG_FILE = 'local/system.json' +EXAMPLE_CONFIG_FILE = 'local/system.json.example' + +LOCAL_DEVICES_DIR = 'local/devices' +RESOURCE_DEVICES_DIR = 'resources/devices' + +DEVICE_CONFIG = 'device_config.json' +DEVICE_MANUFACTURER = 'manufacturer' +DEVICE_MODEL = 'model' +DEVICE_MAC_ADDR = 'mac_addr' +DEVICE_TEST_MODULES = 'test_modules' +DEVICE_TYPE_KEY = 'type' +DEVICE_TECHNOLOGY_KEY = 'technology' +DEVICE_TEST_PACK_KEY = 'test_pack' +DEVICE_ADDITIONAL_INFO_KEY = 'additional_info' +DEVICE_REPORT_NAME_FORMAT = '{mac_addr}_{timestamp}' + +MAX_DEVICE_REPORTS_KEY = 'max_device_reports' + +OLD_REPORTS_FOLDER = 'local/devices/{device_folder}/reports' +REPORTS_FOLDER = 'local/reports' + + +class Testrun: # pylint: disable=too-few-public-methods + """Testrun controller. + + Creates an instance of the network orchestrator, test + orchestrator and user interface. + """ + + def __init__(self, + config_file, + validate=False, + net_only=False, + single_intf=False, + no_ui=False, + target_mac=None, + firmware=None): + + # Locate parent directory + current_dir = os.path.dirname(os.path.realpath(__file__)) + + # Locate the test-run root directory, 4 levels, + # src->python->framework->test-run + self._root_dir = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.dirname(current_dir)))) + + # Determine config file + if config_file is None: + self._config_file = self._get_config_abs(DEFAULT_CONFIG_FILE) + else: + self._config_file = self._get_config_abs(config_file) + + self._net_only = net_only + self._single_intf = single_intf + # Network only option only works if UI is also + # disbled so need to set no_ui if net_only is selected + self._no_ui = no_ui or net_only + + # Catch any exit signals + self._register_exits() + + # Create session + self._session = TestrunSession(root_dir=self._root_dir) + + # Register runtime parameters + if single_intf: + self._session.add_runtime_param('single_intf') + if net_only: + self._session.add_runtime_param('net_only') + if validate: + self._session.add_runtime_param('validate') + + self._net_orc = net_orc.NetworkOrchestrator(session=self._session) + self._test_orc = test_orc.TestOrchestrator(self._session, self._net_orc) + + # Load device repository + self.load_all_devices() + + # If no_ui selected and not network only mode, + # load the target device into the session + if self._no_ui and not net_only: + target_device = self._session.get_device(target_mac) + if target_device is not None: + target_device.firmware = firmware + self._session.set_target_device(target_device) + else: + print( + f'Target device specified does not exist in device registry: ' + f'{target_mac}', + file=sys.stderr) + sys.exit(1) + + # Load test modules + self._test_orc.start() + + # Start websockets server + self.start_ws() + + # Init MQTT client + self._mqtt_client = mqtt.MQTT() + + if self._no_ui: + + # Check Testrun is able to start + if self.get_net_orc().check_config() is False: + return + + # Any additional checks that need to be performed go here + + self.start() + + else: + + # Start UI container + self.start_ui() + + self._api = Api(self) + self._api.start() + + # Hold until API ends + while True: + time.sleep(1) + + def get_root_dir(self): + return self._root_dir + + def get_version(self): + return self.get_session().get_version() + + def load_all_devices(self): + self._session.clear_device_repository() + self._load_devices(device_dir=LOCAL_DEVICES_DIR) + + # Temporarily removing loading of template device + # configs (feature not required yet) + # self._load_devices(device_dir=RESOURCE_DEVICES_DIR) + return self.get_session().get_device_repository() + + def _copy_existing_reports(self, device: Device): + old_reports = self._load_test_reports(device) + device.clear_reports() + if old_reports: + for report in old_reports: + timestamp = report.get_started().strftime('%Y-%m-%dT%H:%M:%S') + report_path = os.path.join(self.get_reports_folder(device), timestamp) + if os.path.exists(report_path) and os.path.isdir(report_path): + new_report_folder_name = DEVICE_REPORT_NAME_FORMAT.format( + mac_addr=device.mac_addr.replace(':', ''), + timestamp=timestamp + ) + new_report_path = os.path.join( + self.get_common_reports_folder(), new_report_folder_name + ) + try: + shutil.copytree( + report_path, + os.path.join(self.get_common_reports_folder(), new_report_path) + ) + except (FileExistsError, shutil.Error) as e: + LOGGER.error(f'Error occurred while copying report: {e}') + report.set_report_url(new_report_folder_name) + report.set_export_url(new_report_folder_name) + device.add_report(report) + self.save_device(device) + try: + shutil.rmtree( + OLD_REPORTS_FOLDER.format(device_folder=device.device_folder) + ) + except FileNotFoundError: + LOGGER.error( + f'Old reports folder not found for device {device.model}' + ) + else: + LOGGER.info('No existing reports to copy') + + def _load_devices(self, device_dir): + LOGGER.debug('Loading devices from ' + device_dir) + + util.run_command(f'chown -R {util.get_host_user()} {device_dir}') + + for device_folder in os.listdir(device_dir): + + device_config_file_path = os.path.join(device_dir, device_folder, + DEVICE_CONFIG) + + # Check if device config file exists before loading + if not os.path.exists(device_config_file_path): + LOGGER.error('Device configuration file missing ' + + f'for device {device_folder}') + continue + + # Open device config file + with open(device_config_file_path, + encoding='utf-8') as device_config_file: + + try: + device_config_json = json.load(device_config_file) + except json.decoder.JSONDecodeError as e: + LOGGER.error('Invalid JSON found in ' + + f'device configuration {device_config_file_path}') + LOGGER.debug(e) + continue + + device_manufacturer = device_config_json.get(DEVICE_MANUFACTURER) + device_model = device_config_json.get(DEVICE_MODEL) + mac_addr = device_config_json.get(DEVICE_MAC_ADDR) + test_modules = device_config_json.get(DEVICE_TEST_MODULES) + reports = device_config_json.get('reports', []) + # Load max device reports + max_device_reports = None + if 'max_device_reports' in device_config_json: + max_device_reports = device_config_json.get(MAX_DEVICE_REPORTS_KEY) + + folder_url = os.path.join(device_dir, device_folder) + + device_reports = [] + if reports: + for report in reports: + test_report = TestReport() + test_report.from_json(report) + device_reports.append(test_report) + + device = Device(folder_url=folder_url, + manufacturer=device_manufacturer, + model=device_model, + mac_addr=mac_addr, + test_modules=test_modules, + max_device_reports=max_device_reports, + device_folder=device_folder, + reports=device_reports + ) + + # Load in the additional fields + if DEVICE_TYPE_KEY in device_config_json: + device.type = device_config_json.get(DEVICE_TYPE_KEY) + + if DEVICE_TECHNOLOGY_KEY in device_config_json: + device.technology = device_config_json.get(DEVICE_TECHNOLOGY_KEY) + + if DEVICE_TEST_PACK_KEY in device_config_json: + device.test_pack = device_config_json.get(DEVICE_TEST_PACK_KEY) + + if DEVICE_ADDITIONAL_INFO_KEY in device_config_json: + device.additional_info = device_config_json.get( + DEVICE_ADDITIONAL_INFO_KEY) + + if None in [device.type, device.technology, device.test_pack]: + LOGGER.warning( + 'Device is outdated and requires further configuration') + device.status = 'Invalid' + + if not device.get_reports(): + self._copy_existing_reports(device) + + # Add device to device repository + self.get_session().add_device(device) + LOGGER.debug(f'Loaded device {device.manufacturer} ' + + f'{device.model} with MAC address {device.mac_addr}') + + def _load_test_reports(self, device): + + LOGGER.debug('Loading test reports for device ' + + f'{device.manufacturer} {device.model}') + + # Remove the existing reports in memory + device.clear_reports() + reports = [] + # Locate reports folder + reports_folder = self.get_reports_folder(device) + + # Check if reports folder exists (device may have no reports) + if not os.path.exists(reports_folder): + return + + for report_folder in os.listdir(reports_folder): + # 1.3 file path + report_json_file_path = os.path.join(reports_folder, report_folder, + 'test', + device.mac_addr.replace(':', ''), + 'report.json') + + if not os.path.isfile(report_json_file_path): + # Revert to pre 1.3 file path + report_json_file_path = os.path.join(reports_folder, report_folder, + 'report.json') + + if not os.path.isfile(report_json_file_path): + # Revert to pre 1.3 file path + report_json_file_path = os.path.join(reports_folder, report_folder, + 'report.json') + + # Check if the report.json file exists + if not os.path.isfile(report_json_file_path): + # Some error may have occurred during this test run + continue + + with open(report_json_file_path, encoding='utf-8') as report_json_file: + report_json = json.load(report_json_file) + test_report = TestReport() + test_report.from_json(report_json) + test_report.set_mac_addr(device.mac_addr) + device.add_report(test_report) + reports.append(test_report) + return reports + + def get_reports_folder(self, device): + """Return the reports folder path for the device""" + return os.path.join(self._root_dir, LOCAL_DEVICES_DIR, device.device_folder, + 'reports') + + def get_common_reports_folder(self): + """Return the common reports folder path for all devices""" + return os.path.join(self._root_dir, REPORTS_FOLDER) + + def delete_report(self, device: Device, report: TestReport) -> bool: + LOGGER.debug(f'Deleting test report for device {device.model} ' + + f'at {report.get_folder_name()}') + + device.remove_report(report) + return True + + def create_device(self, device: Device): + + # Define the device folder location + device_folder_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, + device.device_folder) + + # Create the directory + os.makedirs(device_folder_path) + + config_file_path = os.path.join(device_folder_path, DEVICE_CONFIG) + + with open(config_file_path, 'w', encoding='utf-8') as config_file: + config_file.writelines(json.dumps(device.to_config_json(), indent=4)) + + # Ensure new folder has correct permissions + util.run_command(f"chown -R {util.get_host_user()} '{device_folder_path}'") + + # Add new device to the device repository + self._session.add_device(device) + + return device.to_config_json() + + def save_device(self, device: Device): + """Edit and save an existing device config.""" + + # Obtain the config file path + config_file_path = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, + device.device_folder, DEVICE_CONFIG) + + with open(config_file_path, 'w+', encoding='utf-8') as config_file: + config_file.writelines(json.dumps(device.to_config_json(), indent=4)) + + + return device.to_config_json() + + def delete_device(self, device: Device): + + # Obtain the config file path + device_folder = os.path.join(self._root_dir, LOCAL_DEVICES_DIR, + device.device_folder) + + # Remove device reports + device.remove_reports() + + # Delete the device directory + shutil.rmtree(device_folder) + + # Remove the device from the current session device repository + self.get_session().remove_device(device) + + def start(self): + + self.get_session().start() + + self._start_network() + + self.get_net_orc().get_listener().register_callback( + self._device_discovered, [NetworkEvent.DEVICE_DISCOVERED]) + + if self._net_only: + LOGGER.info('Network only option configured, no tests will be run') + else: + self.get_net_orc().get_listener().register_callback( + self._device_stable, [NetworkEvent.DEVICE_STABLE]) + + self.get_net_orc().start_listener() + self.get_session().set_status(TestrunStatus.WAITING_FOR_DEVICE) + LOGGER.info('Waiting for devices on the network...') + + # Keep application running until stopped + while True: + time.sleep(5) + + async def stop(self): + + # First, change the status to stopping + self.get_session().stop() + + # First, change the status to stopping + self.get_session().stop() + + # Prevent discovering new devices whilst stopping + if self.get_net_orc().get_listener() is not None: + self.get_net_orc().get_listener().stop_listener() + + self._stop_tests() + + self.get_session().set_status(TestrunStatus.CANCELLED) + + # Disconnect before WS server stops to prevent error + self._mqtt_client.disconnect() + + self._stop_network(kill=True) + + def _register_exits(self): + signal.signal(signal.SIGINT, self._exit_handler) + signal.signal(signal.SIGTERM, self._exit_handler) + signal.signal(signal.SIGABRT, self._exit_handler) + signal.signal(signal.SIGQUIT, self._exit_handler) + + def shutdown(self): + LOGGER.info('Shutting down Testrun') + self.stop() + self._stop_ui() + self._stop_ws() + + def _exit_handler(self, signum, arg): # pylint: disable=unused-argument + LOGGER.debug('Exit signal received: ' + str(signum)) + if signum in (2, signal.SIGTERM): + LOGGER.info('Exit signal received.') + self.shutdown() + sys.exit(1) + + def _get_config_abs(self, config_file=None): + if config_file is None: + # If not defined, use relative pathing to local file + config_file = os.path.join(self._root_dir, self._config_file) + + # Expand the config file to absolute pathing + return os.path.abspath(config_file) + + def get_config_file(self): + return self._get_config_abs() + + def get_net_orc(self): + return self._net_orc + + def get_test_orc(self): + return self._test_orc + + def _start_network(self): + # Start the network orchestrator + if not self.get_net_orc().start(): + self.stop() + sys.exit(1) + + def _stop_network(self, kill=True): + self.get_net_orc().stop(kill) + + def _stop_tests(self): + self._test_orc.stop() + + def get_mqtt_client(self): + return self._mqtt_client + + def get_device(self, mac_addr): + """Returns a loaded device object from the device mac address.""" + for device in self.get_session().get_device_repository(): + if device.mac_addr == mac_addr: + return device + return None + + def _device_discovered(self, mac_addr): + + device = self.get_session().get_target_device() + + if device is not None: + if mac_addr != device.mac_addr: + msg_found = f'Found device with mac addr: {mac_addr} but was ignored' + LOGGER.info(msg_found) + msg_expected = f'Expected device mac address is {device.mac_addr}' + LOGGER.info(msg_expected) + full_message = f'{msg_found}\n{msg_expected}' + self._mqtt_client.send_message( + mqtt.MQTTTopic.INFO, {'message': full_message}) + # Ignore discovered device because it is not the target device + return + else: + device = self.get_device(mac_addr) + if device is None: + return + + self.get_session().set_target_device(device) + + LOGGER.info( + f'Discovered {device.manufacturer} {device.model} on the network. ' + + 'Waiting for device to obtain IP') + + def _device_stable(self, mac_addr): + + # Do not continue testing if Testrun has cancelled during monitor phase + if self.get_session().get_status() == TestrunStatus.CANCELLED: + self._stop_network() + return + + LOGGER.info(f'Device with mac address {mac_addr} is ready for testing.') + self._set_status(TestrunStatus.IN_PROGRESS) + + # Start testrun timer + self.get_session().start_timer() + + self._test_orc.run_test_modules() + + self._stop_network() + + def get_session(self): + return self._session + + def _set_status(self, status): + self.get_session().set_status(status) + + def start_ui(self): + + self._stop_ui() + + LOGGER.info('Starting UI') + + client = docker.from_env() + + try: + client.containers.run(image='testrun/ui', + auto_remove=True, + name='tr-ui', + hostname='testrun.io', + detach=True, + ports={'80': 8080}) + except docker.errors.ImageNotFound as ie: + LOGGER.error('An error occurred whilst starting the UI. ' + + 'Please investigate and try again.') + LOGGER.error(ie) + sys.exit(1) + + # TODO: Make port configurable + LOGGER.info('User interface is ready on http://localhost:8080') + + def _stop_ui(self): + LOGGER.info('Stopping user interface') + client = docker.from_env() + try: + container = client.containers.get('tr-ui') + if container is not None: + container.kill() + # If the container has been started without auto-remove flag remove it + try: + container.remove() + except docker.errors.APIError: + pass + except docker.errors.NotFound: + pass + + def start_ws(self): + + self._stop_ws() + + LOGGER.info('Starting WS server') + + client = docker.from_env() + + try: + client.containers.run(image='testrun/ws', + auto_remove=True, + name='tr-ws', + detach=True, + ports={ + '9001': 9001, + '1883': 1883 + }) + except docker.errors.ImageNotFound as ie: + LOGGER.error('An error occurred whilst starting the websockets server. ' + + 'Please investigate and try again.') + LOGGER.error(ie) + sys.exit(1) + + def _stop_ws(self): + LOGGER.info('Stopping websockets server') + client = docker.from_env() + try: + container = client.containers.get('tr-ws') + if container is not None: + container.kill() + # If the container has been started without auto-remove flag remove it + try: + container.remove() + except docker.errors.APIError: + pass + + except docker.errors.NotFound: + pass diff --git a/framework/python/src/test_orc/test_orchestrator.py b/framework/python/src/test_orc/test_orchestrator.py index 7da1d216d..20ed052dc 100644 --- a/framework/python/src/test_orc/test_orchestrator.py +++ b/framework/python/src/test_orc/test_orchestrator.py @@ -20,15 +20,17 @@ import time import shutil import docker -from datetime import datetime -from common import logger, util +from common import logger, util, risk_profile from common.testreport import TestReport from common.statuses import TestrunStatus, TestrunResult, TestResult +from common.device import Device +from core.testrun import REPORTS_FOLDER, DEVICE_REPORT_NAME_FORMAT from core.docker.test_docker_module import TestModule from test_orc.test_case import TestCase from test_orc.test_pack import TestPack import threading from typing import List +from bs4 import BeautifulSoup LOG_NAME = "test_orc" LOGGER = logger.get_logger("test_orc") @@ -45,7 +47,7 @@ MODULE_CONFIG = "conf/module_config.json" SAVED_DEVICE_REPORTS = "report/{device_folder}/" -LOCAL_DEVICE_REPORTS = "local/devices/{device_folder}/reports" +LOCAL_DEVICE_REPORTS = "local/reports" DEVICE_ROOT_CERTS = "local/root_certs" LOG_REGEX = r"^[A-Z][a-z]{2} [0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} test_" @@ -181,12 +183,9 @@ def run_test_modules(self): report.from_json(generated_report_json) report.add_module_reports(self.get_session().get_module_reports()) report.add_module_templates(self.get_session().get_module_templates()) - device.add_report(report) self._write_reports(report) self._test_in_progress = False - self.get_session().set_report_url(report.get_report_url()) - self.get_session().set_export_url(report.get_export_url()) # Set testing description test_pack: TestPack = self.get_test_pack(device.test_pack) @@ -199,19 +198,26 @@ def run_test_modules(self): elif report.get_result() == TestrunResult.NON_COMPLIANT: message = test_pack.get_message("non_compliant_description") + # Move testing output from runtime to local device folder + report_folder_name = self._copy_report_to_common_folder(device) + report.set_report_url(report_folder_name) + report.set_export_url(report_folder_name) + + self.get_session().set_report_url(report.get_report_url()) + self.get_session().set_export_url(report.get_export_url()) + device.add_report(report) + self.get_session().set_description(message) # Set result and status at the end self.get_session().set_result(report.get_result()) self.get_session().set_status(report.get_status()) - # Move testing output from runtime to local device folder - self._timestamp_results(device) - LOGGER.debug("Cleaning old test results...") self._cleanup_old_test_results(device) - LOGGER.debug("Old test results cleaned") + LOGGER.debug("Saving device config...") + device.export_config_json() def _write_reports(self, test_report): @@ -265,12 +271,6 @@ def _generate_report(self): report["status"] = status report["tests"] = self.get_session().get_report_tests() - report["report"] = ( - self._api_url + "/" + SAVED_DEVICE_REPORTS.replace( - "{device_folder}", - device.device_folder) + - self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S")) - report["export"] = report["report"].replace("report", "export") return report @@ -294,7 +294,6 @@ def _cleanup_modules_html_reports(self, out_dir): except Exception as e: LOGGER.error(f"Error {module_template_path}: {e}") - def _cleanup_old_test_results(self, device): if device.max_device_reports is not None: @@ -303,95 +302,56 @@ def _cleanup_old_test_results(self, device): max_device_reports = self.get_session().get_max_device_reports() if max_device_reports > 0: - completed_results_dir = os.path.join( - self._root_path, - LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder)) - - completed_tests = os.listdir(completed_results_dir) - cur_test_count = len(completed_tests) - if cur_test_count > max_device_reports: - LOGGER.debug("Current device has more than max results allowed: " + - str(cur_test_count) + ">" + str(max_device_reports)) - - # Find and delete the oldest test - oldest_test = self._find_oldest_test(completed_results_dir) - if oldest_test is not None: - LOGGER.debug("Oldest test found, removing: " + str(oldest_test[1])) - shutil.rmtree(oldest_test[1], ignore_errors=True) - - # Remove oldest test from session - oldest_timestamp = oldest_test[0] - self.get_session().get_target_device().remove_report(oldest_timestamp) - - # Confirm the delete was succesful - new_test_count = len(os.listdir(completed_results_dir)) - if (new_test_count != cur_test_count - and new_test_count > max_device_reports): - # Continue cleaning up until we're under the max - self._cleanup_old_test_results(device) - - def _find_oldest_test(self, completed_tests_dir): - oldest_timestamp = None - oldest_directory = None - for completed_test in os.listdir(completed_tests_dir): - try: - timestamp = datetime.strptime(str(completed_test), "%Y-%m-%dT%H:%M:%S") + device.sort_reports() + while len(device.get_reports()) > max_device_reports: + report = device.get_reports().pop(0) + report.delete_folder() - # Occurs when time does not match format - except ValueError as e: - LOGGER.error(e) - continue - - if oldest_timestamp is None or timestamp < oldest_timestamp: - oldest_timestamp = timestamp - oldest_directory = completed_test - - if oldest_directory: - return oldest_timestamp, os.path.join(completed_tests_dir, - oldest_directory) - else: - return None - - def _timestamp_results(self, device): + def _copy_report_to_common_folder(self, device: Device) -> str: # Define the current device results directory cur_results_dir = os.path.join(self._root_path, RUNTIME_DIR) # Define the directory - completed_results_dir = os.path.join( - self._root_path, - LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), - self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S")) + report_folder_name = DEVICE_REPORT_NAME_FORMAT.format( + mac_addr=device.mac_addr.replace(":", ""), + timestamp=self.get_session().get_started().strftime("%Y-%m-%dT%H:%M:%S") + ) + report_dir = os.path.join(self._root_path, REPORTS_FOLDER) + report_dir = os.path.join(report_dir, report_folder_name) + LOGGER.info(f"Copying test results from {cur_results_dir} to {report_dir}") # Copy the results to the timestamp directory # leave current copy in place for quick reference to # most recent test - shutil.copytree(cur_results_dir, completed_results_dir, dirs_exist_ok=True) - util.run_command(f"chown -R {self._host_user} '{completed_results_dir}'") + shutil.copytree(cur_results_dir, report_dir, dirs_exist_ok=True) + util.run_command(f"chown -R {self._host_user} '{report_dir}'") # Copy Testrun log to testing directory shutil.copy(os.path.join(self._root_path, "testrun.log"), - os.path.join(completed_results_dir, "testrun.log")) + os.path.join(report_dir, "testrun.log")) - return completed_results_dir + return report_folder_name - def zip_results(self, device, timestamp: str, profile): + def zip_results( + self, device: Device, + report: TestReport, + profile: risk_profile.RiskProfile) -> str: try: LOGGER.debug("Archiving test results") src_path = os.path.join( - LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), - timestamp) + LOCAL_DEVICE_REPORTS, report.get_folder_name()) # Regenerate the report if the device profile has been updated - self._regenerate_report_files(device, timestamp) + self._regenerate_report_files(device, report) # Define temp directory to store files before zipping results_dir = os.path.join(f"/tmp/testrun/{time.time()}") # Define where to save the zip file - zip_location = os.path.join("/tmp/testrun", timestamp) + zip_location = os.path.join("/tmp/testrun", report.get_folder_name()) # Delete zip_temp if it already exists if os.path.exists(results_dir): @@ -431,83 +391,110 @@ def zip_results(self, device, timestamp: str, profile): LOGGER.debug(error) return None - def regenerate_pdf(self, device, timestamp): + def regenerate_pdf(self, device: Device, report: TestReport) -> str: """Regenerate the pdf report""" - self._regenerate_report_files(device, timestamp) - - def _regenerate_report_files(self, device, timestamp): - '''Regenerate the report if the device profile has been updated''' - + return self._regenerate_report_files(device, report) + + def _regenerate_report_files(self, device: Device, report: TestReport) -> str: + """Regenerate the report if the device profile has been updated""" + # Report files path + report_dir = os.path.join(self._root_path, REPORTS_FOLDER) + report_dir = os.path.join(report_dir, report.get_folder_name()) + test_folder_name = report.get_folder_name().split("_")[0] + test_path = os.path.join( + report_dir, + f"test/{test_folder_name}" + ) try: - - # Report files path - report_path = os.path.join( - LOCAL_DEVICE_REPORTS.replace("{device_folder}", device.device_folder), - timestamp, "test", device.mac_addr.replace(":", "")) - - # Parse string timestamp - date_timestamp: datetime.datetime = datetime.strptime( - timestamp, "%Y-%m-%dT%H:%M:%S") - - # Find the report - test_report = None - for report in device.get_reports(): - if report.get_started() == date_timestamp: - test_report = report - - # This should not happen as the timestamp is checked in api.py first - if test_report is None: - return None - # Copy the original report for comparison - test_report_copy = copy.deepcopy(test_report) - - # Update the report with 'additional_info' field - test_report.update_device_profile(device.additional_info) - + report_copy = copy.deepcopy(report) + # Update the report with additional_info field + report.update_device_info(device) + device.export_config_json() # Overwrite report only if additional_info has been changed - if test_report.to_json() != test_report_copy.to_json(): + if report.to_json() != report_copy.to_json(): LOGGER.debug("Device profile has been updated, regenerating the report") - # Store the jinja templates - reload_templates = [] - - # Load the jinja templates - if os.path.isdir(report_path): - for dir_path, _, filenames in os.walk(report_path): - for filename in filenames: - try: - if filename.endswith(".j2.html"): - with open(os.path.join(dir_path, filename), "r", - encoding="utf-8") as f: - reload_templates.append(f.read()) - except Exception as e: - LOGGER.debug(f"Could not read the file: {e}") - - # Add the jinja templates to the report - test_report.add_module_templates(reload_templates) - # Rewrite the json report - with open(os.path.join(report_path, "report.json"), + with open(os.path.join(test_path, "report.json"), "w", encoding="utf-8") as f: - json.dump(test_report.to_json(), f, indent=2) + json.dump(report.to_json(), f, indent=2) + with open(os.path.join(test_path, "report.html"), + "r", + encoding="utf-8") as f: + html = f.read() + html = self._update_html_report(report, html) + LOGGER.debug(f"{test_path}") # Rewrite the html report - with open(os.path.join(report_path, "report.html"), + with open(os.path.join(test_path, "report.html"), "w", encoding="utf-8") as f: - f.write(test_report.to_html()) + f.write(html) # Rewrite the pdf report - with open(os.path.join(report_path, "report.pdf"), "wb") as f: - f.write(test_report.to_pdf().getvalue()) + with open(os.path.join(test_path, "report.pdf"), "wb") as f: + f.write(report.to_pdf_from_html(html).getvalue()) LOGGER.debug("Report has been regenerated") except Exception as error: LOGGER.error("Failed to regenerate the report") LOGGER.debug(error) + return test_path + + def _update_html_report(self, report: TestReport, html: str): + """Update the HTML report with the new device information.""" + report_json = report.to_json() + manufacturer = report_json["device"].get("manufacturer", "Unknown") + model = report_json["device"].get("model", "Unknown") + title = f"{manufacturer} {model}" + bs = BeautifulSoup(html, "html.parser") + div_profile = bs.find("div", class_="device-profile-content") + if div_profile is not None: + for item in report_json["device"]["device_profile"]: + question = item["question"] + answer = item["answer"] + # Find the question div with exact text match for 'q' + question_div = div_profile.find( + "div", + class_="device-profile-question", + string=question + ) + if question_div: + # Find the next sibling answer div + answer_div = question_div.find_next_sibling( + "div", + class_="device-profile-answer" + ) + if answer_div: + # Update the answer text with 'a' + answer_div.string = answer + h3 = bs.find("h3", class_="header-info-device") + if h3: + h3.string = title + # Update manufacturer and model in summary section by finding labels + all_labels = bs.find_all("div", class_="summary-item-label") + for label_div in all_labels: + h4 = label_div.find("h4") + if h4 and h4.string: + # Find the next summary-item-value sibling + value_div = label_div.find_next_sibling( + "div", + class_="summary-item-value" + ) + if value_div: + if "Manufacturer" in h4.string: + value_div.string = manufacturer + elif "Model" in h4.string: + value_div.string = model + all_header_info_divs = bs.find_all("div", class_="header-info") + for header_info_div in all_header_info_divs: + header_span = header_info_div.find_next_sibling("span") + if header_span: + header_span.string = title + return str(bs) def test_in_progress(self): return self._test_in_progress diff --git a/framework/requirements.txt b/framework/requirements.txt index 271271988..5361caef1 100644 --- a/framework/requirements.txt +++ b/framework/requirements.txt @@ -1,5 +1,5 @@ # Requirements for the core module -requests==2.32.5 +requests==2.33.0 # Requirements for the net_orc module docker==7.1.0 @@ -15,7 +15,7 @@ pydyf==0.11.0 fastapi==0.115.0 psutil==5.9.8 uvicorn==0.27.0 -python-multipart==0.0.19 +python-multipart==0.0.22 pydantic==2.10.0 # Requirements for testing @@ -28,7 +28,7 @@ responses==0.25.3 markdown==3.5.2 # Requirements for the session -cryptography==44.0.1 +cryptography==46.0.6 pytz==2024.1 # Requirements for the risk profile @@ -43,3 +43,4 @@ APScheduler==3.10.4 # Requirements for reports generation Jinja2==3.1.6 beautifulsoup4==4.12.3 +html5lib==1.1 diff --git a/make/DEBIAN/control b/make/DEBIAN/control index a43deb455..9637d491b 100644 --- a/make/DEBIAN/control +++ b/make/DEBIAN/control @@ -1,5 +1,5 @@ Package: Testrun -Version: 2.3.3 +Version: 2.3.4 Architecture: amd64 Maintainer: Google Homepage: https://github.com/google/testrun diff --git a/modules/network/base/python/requirements.txt b/modules/network/base/python/requirements.txt index dc4b53d88..58bcd7b34 100644 --- a/modules/network/base/python/requirements.txt +++ b/modules/network/base/python/requirements.txt @@ -1,7 +1,7 @@ # Dependencies to user defined packages # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies -protobuf==6.32.1 +protobuf==6.33.5 # User defined packages grpcio==1.75.1 diff --git a/modules/test/base/python/requirements.txt b/modules/test/base/python/requirements.txt index 8d71b2d85..f00be89cb 100644 --- a/modules/test/base/python/requirements.txt +++ b/modules/test/base/python/requirements.txt @@ -1,7 +1,7 @@ # Dependencies to user defined packages # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies -protobuf==6.32.1 +protobuf==6.33.5 # User defined packages grpcio==1.75.1 diff --git a/modules/test/conn/conf/module_config.json b/modules/test/conn/conf/module_config.json index a992da1a5..13cfff786 100644 --- a/modules/test/conn/conf/module_config.json +++ b/modules/test/conn/conf/module_config.json @@ -16,17 +16,27 @@ { "name": "connection.port_link", "test_description": "The network switch port connected to the device has an active link without errors", - "expected_behavior": "When the ethernet cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches." + "expected_behavior": "When the ethernet cable is connected to the port, the device triggers the port to its enabled \"Link UP\" (LEDs illuminate on device and switch ports if present) state, and the switch shows no errors with the LEDs and when interrogated with a \"show interface\" command on most network switches.", + "recommendations": [ + "Check the physical connection and integrity of the Ethernet cable", + "Verify that the switch port is in an up state" + ] }, { "name": "connection.port_speed", "test_description": "The network switch port connected to the device has auto-negotiated a speed that is 10 Mbps or higher", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\"." + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a speed that can be checked with the \"show interface\" command on most network switches. The output of this command must also show that the \"configured speed\" is set to \"auto\".", + "recommendations": [ + "Ensure Auto-negotiation is enabled on both the device and the switch port" + ] }, { "name": "connection.port_duplex", "test_description": "The network switch port connected to the device has auto-negotiated full-duplex", - "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection." + "expected_behavior": "When the ethernet cable is connected to the port, the device autonegotiates a full-duplex connection.", + "recommendations": [ + "Ensure Auto-negotiation is enabled on both the device and the switch port" + ] }, { "name": "connection.switch.arp_inspection", @@ -52,7 +62,11 @@ { "name": "connection.switch.dhcp_snooping", "test_description": "The device operates as a DHCP client and operates correctly when DHCP snooping is enabled on a switch.", - "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch." + "expected_behavior": "Device continues to operate correctly when DHCP snooping is enabled on the switch.", + "recommendations": [ + "Verify that the switch port is correctly configured as 'untrusted' for the client", + "Ensure the DHCP server is connected to a 'trusted' port on the switch" + ] }, { "name": "connection.dhcp_address", @@ -126,12 +140,21 @@ { "name": "connection.dhcp_disconnect", "test_description": "The device under test issues a new DHCPREQUEST packet after a port physical disconnection and reconnection", - "expected_behavior": "A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network address until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem." + "expected_behavior": "A client SHOULD use DHCP to reacquire or verify its IP address and network parameters whenever the local network parameters may have changed; e.g., at system boot time or after a disconnection from the local network, as the local network configuration may change without the client's or user's knowledge. If a client has knowledge ofa previous network address and is unable to contact a local DHCP server, the client may continue to use the previous network address until the lease for that address expires. If the lease expires before the client can contact a DHCP server, the client must immediately discontinue use of the previous network address and may inform local users of the problem.", + "recommendations": [ + "Verify that the device's network stack correctly detects the Link Down/Link Up events", + "Check if the device OS/firmware is configured to renew DHCP on link state change" + ] }, { "name": "connection.dhcp_disconnect_ip_change", "test_description": "When device is disconnected, update device IP on the DHCP server and reconnect the device. Ensure device received new IP address", - "expected_behavior": "If IP address for a device was changed on the DHCP server while the device was disconnected then the device should request and update the new IP upon reconnecting to the network" + "expected_behavior": "If IP address for a device was changed on the DHCP server while the device was disconnected then the device should request and update the new IP upon reconnecting to the network", + "recommendations": [ + "Ensure the device does not ignore DHCPNAK messages from the server", + "Verify the device's DHCP client behavior when the 'Requested IP' is no longer available", + "Check if the device is caching old IP parameters longer than the Lease Time allows" + ] }, { "name": "connection.single_ip", diff --git a/modules/test/conn/python/requirements.txt b/modules/test/conn/python/requirements.txt index 04480305b..0eaca2e07 100644 --- a/modules/test/conn/python/requirements.txt +++ b/modules/test/conn/python/requirements.txt @@ -1,12 +1,12 @@ # Dependencies to user defined packages # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies -cffi==1.17.1 -cryptography==44.0.1 +cffi==2.0.0 +cryptography==46.0.6 pycparser==2.22 six==1.16.0 # User defined packages -pyOpenSSL==24.3.0 +pyOpenSSL==25.3.0 scapy==2.7.0 python-dateutil==2.9.0.post0 diff --git a/modules/test/dns/conf/module_config.json b/modules/test/dns/conf/module_config.json index 662273cd7..6049c9654 100644 --- a/modules/test/dns/conf/module_config.json +++ b/modules/test/dns/conf/module_config.json @@ -33,7 +33,12 @@ { "name": "dns.mdns", "test_description": "Does the device have MDNS (or any kind of IP multicast)", - "expected_behavior": "Device may send MDNS requests" + "expected_behavior": "Device may send MDNS requests", + "recommendations": [ + "Enable mDNS support in the device network configuration", + "Check if the network switch has IGMP Snooping enabled, which might block multicast", + "Ensure the device is on a network segment that allows multicast traffic" + ] } ] } diff --git a/modules/test/ntp/conf/module_config.json b/modules/test/ntp/conf/module_config.json index ca69898d1..7982edb9e 100644 --- a/modules/test/ntp/conf/module_config.json +++ b/modules/test/ntp/conf/module_config.json @@ -23,85 +23,14 @@ }, { "name": "ntp.network.ntp_dhcp", - "test_description": "Accept NTP address over DHCP", - "expected_behavior": "Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET)", - "config": { - "pools_with_subdomains": [ - "pool.ntp.org", - "europe.pool.ntp.org", - "asia.pool.ntp.org", - "north-america.pool.ntp.org", - "south-america.pool.ntp.org", - "oceania.pool.ntp.org", - "africa.pool.ntp.org", - "us.pool.ntp.org", - "de.pool.ntp.org", - "fr.pool.ntp.org", - "uk.pool.ntp.org", - "jp.pool.ntp.org", - "cn.pool.ntp.org", - "br.pool.ntp.org", - "in.pool.ntp.org", - "za.pool.ntp.org", - "au.pool.ntp.org", - "ca.pool.ntp.org", - "ch.pool.ntp.org", - "it.pool.ntp.org", - "es.pool.ntp.org", - "pl.pool.ntp.org", - "nl.pool.ntp.org", - "se.pool.ntp.org", - "fi.pool.ntp.org", - "dk.pool.ntp.org", - "no.pool.ntp.org", - "be.pool.ntp.org", - "pt.pool.ntp.org", - "tr.pool.ntp.org", - "gr.pool.ntp.org", - "cz.pool.ntp.org", - "sk.pool.ntp.org", - "hu.pool.ntp.org", - "ro.pool.ntp.org", - "bg.pool.ntp.org", - "lt.pool.ntp.org", - "lv.pool.ntp.org", - "ee.pool.ntp.org", - "ua.pool.ntp.org", - "rs.pool.ntp.org", - "hr.pool.ntp.org", - "si.pool.ntp.org", - "ntp.ubuntu.pool.ntp.org", - "debian.pool.ntp.org" - ], - "single_servers": [ - "ntp.ubuntu.com", - "ntp.debian.org", - "time.google.com", - "time.cloudflare.com", - "time.windows.com", - "time.apple.com", - "time.nist.gov", - "utcnist.colorado.edu", - "ntp1.ptb.de", - "ntp2.ptb.de", - "ntp3.ptb.de", - "ntp1.npl.co.uk", - "ntp2.npl.co.uk", - "ntp.nict.jp", - "ntp1.nict.jp", - "ntp2.nict.jp", - "ntp1.t-online.de", - "ntp2.t-online.de", - "ntp3.t-online.de", - "ntp4.t-online.de" - ], - "dns_servers": [ - "8.8.8.8", - "1.1.1.1" - ] - }, + "test_description": "Accept NTP address over DHCP or from public trusted sources", + "expected_behavior": "Device can accept NTP server address, provided by the DHCP server (DHCP OFFER PACKET) or from public trusted sources.", "recommendations": [ - "Install an NTP client that supports fetching the NTP servers from DHCP options" + "Install an NTP client that supports fetching the NTP servers from DHCP options", + "Verify that the device is configured to accept DHCP Option 42", + "Delete any hardcoded public IPs", + "Change the NTP setting from 'Static/Manual' to 'Authomatic/DCHP'", + "Restart the NTP daemon" ] } ] diff --git a/modules/test/ntp/python/requirements.txt b/modules/test/ntp/python/requirements.txt index e79914e8b..dc5c39f2f 100644 --- a/modules/test/ntp/python/requirements.txt +++ b/modules/test/ntp/python/requirements.txt @@ -5,4 +5,5 @@ # User defined packages scapy==2.7.0 pyshark==0.6 -dnspython==2.8.0 \ No newline at end of file +aiohttp==3.13.5 +ntplib==0.4.0 \ No newline at end of file diff --git a/modules/test/ntp/python/src/ntp_module.py b/modules/test/ntp/python/src/ntp_module.py index 91a5f9376..e40f66e9d 100644 --- a/modules/test/ntp/python/src/ntp_module.py +++ b/modules/test/ntp/python/src/ntp_module.py @@ -19,7 +19,7 @@ from collections import defaultdict from jinja2 import Environment, FileSystemLoader import pyshark -from ntp_white_list import NTPWhitelistResolver +from ntp_white_list import check_all_ips LOG_NAME = 'test_ntp' MODULE_REPORT_FILE_NAME = 'ntp_report.j2.html' @@ -319,16 +319,8 @@ def _ntp_network_ntp_support(self): LOGGER.info(result[1]) return result - def _ntp_network_ntp_dhcp(self, config): + def _ntp_network_ntp_dhcp(self): LOGGER.info('Running ntp.network.ntp_dhcp') - try: - ntp_whitelist_resolver = NTPWhitelistResolver( - config=config, - logger=LOGGER - ) - except Exception as e: - LOGGER.error(f'Error initializing NTPWhitelistResolver: {e}') - return 'Error', 'Failed to initialize NTP whitelist resolver' # Read the pcap files packet_capture = (rdpcap(self.startup_capture_file) + @@ -360,36 +352,50 @@ def _ntp_network_ntp_dhcp(self, config): LOGGER.info('Device sent NTP request to non-DHCP provided NTP server') ntp_to_remote = True ntp_to_remote_ips.add(dest_ip) + ips_trusted = [] + all_ips_trusted = False + if ntp_to_remote_ips: + ips_trusted = check_all_ips(list(ntp_to_remote_ips)) + LOGGER.debug(f'Checked NTP remote IPs: {ips_trusted}') + all_ips_trusted = all(is_trusted for _, is_trusted in ips_trusted) + - ntp_to_remote_trusted = bool(ntp_to_remote_ips) and all( - ntp_whitelist_resolver.is_ip_whitelisted(ip) for ip in ntp_to_remote_ips - ) + result_details = [f'NTP request to {self._ntp_server}'] - for ip in ntp_to_remote_ips: - if ntp_whitelist_resolver.is_ip_whitelisted(ip): - LOGGER.info(f'NTP server {ip} is in the trusted whitelist') + for ip, is_trusted in ips_trusted: + if is_trusted: + LOGGER.info(f'NTP server {ip} is trusted') else: - LOGGER.info(f'NTP server {ip} is NOT in the trusted whitelist') + LOGGER.info(f'NTP server {ip} is NOT trusted') + result_details.append(f'NTP request to {ip}') - result = 'Feature Not Detected', 'Device has not sent any NTP requests' + result_state = 'Feature Not Detected' + result_message = 'Device has not sent any NTP requests' if device_sends_ntp: if ntp_to_local and ntp_to_remote: - if ntp_to_remote_trusted: - result = True, ('Device sent NTP request to DHCP provided ' + + if all_ips_trusted: + result_state = True + result_message = ('Device sent NTP request to DHCP provided ' + 'server and trusted non-DHCP provided servers') else: - result = False, ('Device sent NTP request to DHCP provided ' + + result_state = False + result_message = ('Device sent NTP request to DHCP provided ' + 'server and to untrusted non-DHCP provided server') elif ntp_to_remote: - if ntp_to_remote_trusted: - result = False, ('Device sent NTP request to trusted ' + + if all_ips_trusted: + result_state = False + result_message = ('Device sent NTP request to trusted ' + 'non-DHCP provided server') else: - result = False, ('Device sent NTP request to untrusted ' + + result_state = False + result_message = ('Device sent NTP request to untrusted ' + 'non-DHCP provided server') elif ntp_to_local: - result = True, 'Device sent NTP request to DHCP provided server' + result_state = True + result_message ='Device sent NTP request to DHCP provided server' - LOGGER.info(result[1]) - return result + if not ntp_to_local: + result_details.pop(0) + + return result_state, result_message, result_details diff --git a/modules/test/ntp/python/src/ntp_white_list.py b/modules/test/ntp/python/src/ntp_white_list.py index 51269a235..90979a1c4 100644 --- a/modules/test/ntp/python/src/ntp_white_list.py +++ b/modules/test/ntp/python/src/ntp_white_list.py @@ -1,118 +1,65 @@ """Module to resolve NTP whitelist domains to IP addresses asynchronously.""" import asyncio +import aiohttp import concurrent.futures -import dns.asyncresolver -from logging import Logger +import ntplib +NTP_URL = 'https://ntppool.org/scores/{ip}/json' -class NTPWhitelistResolver: - """Class to resolve NTP whitelist domains to IP addresses.""" - def __init__(self, - config: dict, - logger: Logger, - semaphore_limit: int = 50, - timeout: int = 30 - ): - self.config = config - self.semaphore_limit = semaphore_limit - self.timeout = timeout - self._logger = logger - self._ip_whitelist = self._get_ntp_whitelist_ips() +async def fetch_ntp_status( + session: aiohttp.ClientSession, + ip: str + ) -> tuple[str, bool]: + """Fetch the NTP status for a single IP address.""" + try: + async with session.get(NTP_URL.format(ip=ip), timeout=10) as response: + if response.status == 200: + data = await response.json() + score = data['monitors'][0].get('score', 0) + active = score >= 10 + return (ip, active) + else: + return (ip, False) + except Exception: + return (ip, False) - # Create the final list of NTP domains to resolve - def _create_final_ntp_domain_list( - self, - pools_with_subdomains: list[str], - single_servers: list[str] - ) -> list[str]: - ntp_domains = [] - for pool in pools_with_subdomains: - for i in range(4): - ntp_domains.append(f"{i}.{pool}") - ntp_domains.append(pool) - ntp_domains.extend(single_servers) - return ntp_domains - # Resolve a domain to its IP addresses - async def _resolve_domain( - self, domain: str, dns_servers: list[str], attempts: int = 2 - ) -> set[str]: - ips = set() - for _ in range(attempts): - for dns_server in dns_servers: - resolver = dns.asyncresolver.Resolver() - resolver.nameservers = [dns_server] - try: - answers = await resolver.resolve(domain, "A", lifetime=2) - for rdata in answers: - ips.add(rdata.address) - except Exception: - pass - try: - answers6 = await resolver.resolve(domain, "AAAA", lifetime=2) - for rdata in answers6: - ips.add(rdata.address) - except Exception: - pass - return ips +async def _check_all_ips_async(ip_list: list[str]) -> list[tuple[str, bool]]: + """Check NTP status for all IPs asynchronously.""" + async with aiohttp.ClientSession() as session: + tasks = [fetch_ntp_status(session, ip) for ip in ip_list] + # Run all tasks concurrently + results = await asyncio.gather(*tasks) + return results - async def _sem_task( - self, - domain: str, - dns_servers: list[str], - semaphore: asyncio.Semaphore - ) -> set[str]: - async with semaphore: - return await self._resolve_domain(domain, dns_servers) +def _get_ntp_data(ip): + """Check NTP status for a single IP address.""" + client = ntplib.NTPClient() + try: + client.request(ip, version=3, timeout=2) + return True + except ntplib.NTPException: + return False - # Get IPs for NTP whitelist - async def _get_ips_whitelist( - self, config, semaphore_limit: int, timeout: int - ) -> set[str]: - pools_with_subdomains = config.get("pools_with_subdomains", []) - single_servers = config.get("single_servers", []) - dns_servers = config.get("dns_servers", []) - ntp_domain_list = self._create_final_ntp_domain_list( - pools_with_subdomains, single_servers - ) - semaphore = asyncio.Semaphore(semaphore_limit) - tasks = [self._sem_task( - domain, - dns_servers, - semaphore) - for domain in ntp_domain_list - ] - all_ips = set() - for coro in asyncio.as_completed(tasks, timeout=timeout): - try: - ips = await coro - if isinstance(ips, set): - all_ips.update(ips) - except asyncio.TimeoutError: - break - self._logger.info(f"Added {len(all_ips)} IPs to NTP whitelist.") - return all_ips - def _get_ntp_whitelist_ips( - self, - semaphore_limit: int = 50, - timeout: int = 30 - ) -> set[str]: - # Always run in a separate thread to ensure we have a clean event loop - def run_in_thread(): - new_loop = asyncio.new_event_loop() - asyncio.set_event_loop(new_loop) - try: - return new_loop.run_until_complete( - self._get_ips_whitelist(self.config, semaphore_limit, timeout)) - finally: - new_loop.close() +def check_all_ips(ip_list: list[str]) -> list[tuple[str, bool]]: + """Check NTP status for all IPs in a separate thread.""" + def run_in_thread(): + new_loop = asyncio.new_event_loop() + asyncio.set_event_loop(new_loop) + try: + return new_loop.run_until_complete(_check_all_ips_async(ip_list)) + finally: + new_loop.close() - with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: - return executor.submit(run_in_thread).result() - - # Check if an IP is whitelisted - def is_ip_whitelisted(self, ip: str) -> bool: - return ip in self._ip_whitelist + with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor: + future = executor.submit(run_in_thread) + ntps = future.result() + for i in range(len(ntps)): + ip, trusted = ntps[i] + if not trusted: + trusted = _get_ntp_data(ip) + ntps[i] = (ip, trusted) + return ntps diff --git a/modules/test/protocol/conf/module_config.json b/modules/test/protocol/conf/module_config.json index 256588f40..37598b644 100644 --- a/modules/test/protocol/conf/module_config.json +++ b/modules/test/protocol/conf/module_config.json @@ -16,12 +16,21 @@ { "name": "protocol.valid_bacnet", "test_description": "Can valid BACnet traffic be seen", - "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed" + "expected_behavior": "BACnet traffic can be seen on the network and packets are valid and not malformed", + "recommendations": [ + "Inspect network traffic for malformed packets or checksum errors", + "Verify that the MTU settings are consistent across the network path" + ] }, { "name": "protocol.bacnet.version", "test_description": "Obtain the version of BACnet client used", - "expected_behavior": "The BACnet client implements an up to date version of BACnet" + "expected_behavior": "The BACnet client implements an up to date version of BACnet", + "recommendations": [ + "Update the device firmware", + "Inspect network traffic for malformed packets or checksum errors", + "Ensure the device supports the required ASHRAE 135 protocol revision" + ] }, { "name": "protocol.valid_modbus", diff --git a/modules/test/protocol/python/requirements.txt b/modules/test/protocol/python/requirements.txt index 1fe889fe9..4d9dc64b9 100644 --- a/modules/test/protocol/python/requirements.txt +++ b/modules/test/protocol/python/requirements.txt @@ -1,13 +1,13 @@ # Dependencies to user defined packages # Package dependencies should always be defined before the user defined # packages to prevent auto-upgrades of stable dependencies -bacpypes==0.18.7 +bacpypes3==0.0.104 colorama==0.4.6 # User defined packages # Required for BACnet protocol tests netifaces==0.11.0 -BAC0==23.7.3 +BAC0==2025.9.15 pytz==2024.2 # Required for Modbus protocol tests diff --git a/modules/test/protocol/python/src/protocol_bacnet.py b/modules/test/protocol/python/src/protocol_bacnet.py index 3b6d8f0ce..aaa41b224 100644 --- a/modules/test/protocol/python/src/protocol_bacnet.py +++ b/modules/test/protocol/python/src/protocol_bacnet.py @@ -14,6 +14,7 @@ """Module to run all the BACnet related methods for testing""" import BAC0 +from dataclasses import dataclass import logging import json from common import util @@ -29,15 +30,25 @@ DEFAULT_CAPTURE_FILE = 'protocol.pcap' DEFAULT_BIN_DIR = '/testrun/bin' + +@dataclass +class BACnetDevice: + device_id: str + ip: str + + class BACnet(): """BACnet Test module""" + devices: list[BACnetDevice] = [] + bacnet: BAC0.lite def __init__(self, log, + device_hw_addr, captures_dir=DEFAULT_CAPTURES_DIR, capture_file=DEFAULT_CAPTURE_FILE, bin_dir=DEFAULT_BIN_DIR, - device_hw_addr=None): + ): # Set the log global LOGGER LOGGER = log @@ -51,23 +62,30 @@ def __init__(self, self._capture_file = capture_file self._bin_dir = bin_dir self.device_hw_addr = device_hw_addr - self.devices = [] - self.bacnet = None self._bin_dir = bin_dir - def discover(self, local_ip=None): + async def discover(self, local_ip): LOGGER.info('Performing BACnet discovery...') self.bacnet = BAC0.lite(local_ip) LOGGER.info('Local BACnet object: ' + str(self.bacnet)) try: - self.bacnet.discover(global_broadcast=True) - except Exception as e: # pylint: disable=W0718 + await self.bacnet._discover(global_broadcast=True) # pylint: disable=protected-access + except Exception as e: # pylint: disable=W0718 LOGGER.error(e) LOGGER.info('BACnet discovery complete') with open(BAC0_LOG, 'r', encoding='utf-8') as f: bac0_log = f.read() LOGGER.info('BAC0 Log:\n' + bac0_log) - self.devices = self.bacnet.devices + # Extract discovered devices as a BACnetDevice. + self.devices = [] + if self.bacnet.discoveredDevices is not None: + for device_info in self.bacnet.discoveredDevices.values(): + self.devices.append( + BACnetDevice( + device_id=str(device_info['object_instance'][1]), + ip=str(device_info['address']) + ) + ) LOGGER.info('BACnet devices found: ' + str(len(self.devices))) # Check if the device being tested is in the discovered devices list @@ -79,10 +97,9 @@ def validate_device(self): if len(self.devices) > 0: result = True for device in self.devices: - object_id = str(device[3]) # BACnet Object ID - LOGGER.info('Checking device: ' + str(device)) + LOGGER.info(f'Checking device: {device.device_id}') device_valid = self.validate_bacnet_source( - object_id=object_id, device_hw_addr=self.device_hw_addr) + device=device, device_hw_addr=self.device_hw_addr) if device_valid is not None: result &= device_valid description = ('BACnet device discovered' if result else @@ -95,17 +112,35 @@ def validate_device(self): LOGGER.error('Error occurred when validating device', exc_info=True) return result, description - - def validate_protocol_version(self, device_addr, device_id): - LOGGER.info(f'Resolving protocol version for BACnet device: {device_id}') + async def validate_protocol_version( + self, + device: BACnetDevice + ) -> tuple[bool, str]: + LOGGER.info( + f'Resolving protocol version for BACnet device: {device.device_id}' + ) try: - version = self.bacnet.read( - f'{device_addr} device {device_id} protocolVersion') - revision = self.bacnet.read( - f'{device_addr} device {device_id} protocolRevision') - protocol_version = f'{version}.{revision}' - result = True - result_description = f'Device uses BACnet version {protocol_version}' + dut = await BAC0.device( + device.ip, + device.device_id, + self.bacnet + ) + version = await dut.read_property( + ('device', device.device_id, 'protocolVersion') + ) + revision = await dut.read_property( + ('device', device.device_id, 'protocolRevision') + ) + if version is None or revision is None: + result = False + result_description = ( + f'Failed to resolve protocol version: version={version}, ' + f'revision={revision}') + LOGGER.error(result_description) + else: + protocol_version = f'{version}.{revision}' + result = True + result_description = f'Device uses BACnet version {protocol_version}' except (UnknownPropertyError, ReadPropertyException, NoResponseFromController, DeviceNotConnected) as e: result = False @@ -115,17 +150,23 @@ def validate_protocol_version(self, device_addr, device_id): # Validate that all traffic to/from BACnet device from # discovered object id matches the MAC address of the device - def validate_bacnet_source(self, object_id, device_hw_addr): + def validate_bacnet_source( + self, device: BACnetDevice, + device_hw_addr: str + ) -> bool: try: - LOGGER.info(f'Checking BACnet traffic for object id {object_id}') + LOGGER.info(f'Checking BACnet traffic for object id {device.device_id}') capture_file = os.path.join(self._captures_dir, self._capture_file) - packets = self.get_bacnet_packets(capture_file, object_id) + packets = self.get_bacnet_packets(capture_file, device) valid = None # If no packets are found in protocol.pcap if not packets: - LOGGER.debug(f'No BACnet packets found for object id {object_id}') + LOGGER.debug( + f'No BACnet packets found for object id {device.device_id}' + ) for packet in packets: - if object_id in packet['_source']['layers']['bacapp.instance_number']: + pakcet_bac = packet['_source']['layers']['bacapp.instance_number'] + if device.device_id in pakcet_bac: if device_hw_addr.lower() in packet['_source']['layers']['eth.src']: LOGGER.debug('BACnet detected from device') valid = True if valid is None else valid and True @@ -143,9 +184,13 @@ def validate_bacnet_source(self, object_id, device_hw_addr): LOGGER.error('Error occurred when validating source', exc_info=True) return False - def get_bacnet_packets(self, capture_file, object_id): + def get_bacnet_packets( + self, + capture_file: str, + device: BACnetDevice + ) -> list[dict]: bin_file = self._bin_dir + '/get_bacnet_packets.sh' - args = f'"{capture_file}" {object_id}' + args = f'"{capture_file}" {device.device_id}' command = f'{bin_file} {args}' response = util.run_command(command) return json.loads(response[0].strip()) diff --git a/modules/test/protocol/python/src/protocol_module.py b/modules/test/protocol/python/src/protocol_module.py index 92891465d..95a43a7c9 100644 --- a/modules/test/protocol/python/src/protocol_module.py +++ b/modules/test/protocol/python/src/protocol_module.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Protocol test module""" +import asyncio from test_module import TestModule import netifaces from protocol_bacnet import BACnet @@ -26,18 +27,23 @@ class ProtocolModule(TestModule): def __init__(self, module): self._supports_bacnet = False + self._bacnet_loop = None super().__init__(module_name=module, log_name=LOG_NAME) global LOGGER LOGGER = self._get_logger() self._bacnet = BACnet(log=LOGGER, device_hw_addr=self._device_mac) + def _get_bacnet_loop(self): + if self._bacnet_loop is None: + self._bacnet_loop = asyncio.new_event_loop() + return self._bacnet_loop + def _protocol_valid_bacnet(self): LOGGER.info('Running protocol.valid_bacnet') result = None interface_name = 'veth0' # If the ipv4 address wasn't resolved yet, try again - if self._device_ipv4_addr is None: - self._device_ipv4_addr = self._get_device_ipv4() + self._device_ipv4_addr = self._get_device_ipv4() if self._device_ipv4_addr is None: LOGGER.error('No device IP could be resolved') @@ -46,7 +52,9 @@ def _protocol_valid_bacnet(self): # Resolve the appropriate IP for BACnet comms local_address = self.get_local_ip(interface_name) if local_address: - self._bacnet.discover(local_address + '/24') + local_address += '/24' + self._get_bacnet_loop().run_until_complete( + self._bacnet.discover(local_address)) result = self._bacnet.validate_device() if result[0]: self._supports_bacnet = True @@ -73,10 +81,11 @@ def _protocol_bacnet_version(self): if len(self._bacnet.devices) > 0: for device in self._bacnet.devices: LOGGER.debug(f'Checking BACnet version for device: {device}') - device_addr = device[2] - device_id = device[3] + loop = self._get_bacnet_loop() result_status, result_description = \ - self._bacnet.validate_protocol_version(device_addr,device_id) + loop.run_until_complete( + self._bacnet.validate_protocol_version(device) + ) break LOGGER.info(result_description) diff --git a/modules/test/services/README.md b/modules/test/services/README.md index f6133cc6e..000b600de 100644 --- a/modules/test/services/README.md +++ b/modules/test/services/README.md @@ -24,4 +24,5 @@ Within the ```python/src``` directory, the below tests are executed. | security.services.snmpv3 | Check SNMP port 161/162 is disabled. If SNMP is an essential service, it should be v3 | Device is unreachable on port 161/162 unless SNMP is essential in which case it is SNMPv3 that is used | Required | | security.services.vnc | Check VNC is disabled on any port | Device cannot be accessed via VNC on any port | Required | | security.services.tftp | Check TFTP port 69 is disabled (UDP) | There is no TFTP service running on any port | Required | -| ntp.network.ntp_server | Check NTP port 123 is disabled and the device is not acting as an NTP server | The devices does not respond to NTP requests | Required | \ No newline at end of file +| ntp.network.ntp_server | Check NTP port 123 is disabled and the device is not acting as an NTP server | The devices does not respond to NTP requests | Required | +| security.services.bacnet | Checks if a BACnet server is running on any port | Reports presence of BACnet server and the ports it is running on | Informational | \ No newline at end of file diff --git a/modules/test/services/conf/module_config.json b/modules/test/services/conf/module_config.json index 6c481821e..b9190a4f7 100644 --- a/modules/test/services/conf/module_config.json +++ b/modules/test/services/conf/module_config.json @@ -377,6 +377,12 @@ "name": "protocol.services.bacnet", "test_description": "Report whether the device is running a BACnet server", "expected_behavior": "The device may or may not be running a BACnet server", + "recommendations": [ + "Verify if the BACnet service is enabled in the device settings", + "Check if UDP port 47808 is open and not blocked by a firewall", + "Ensure the BACnet stack is properly initialized on the device", + "Ensure the BACnet service is running on the same network interface to which the device under test is connected" + ], "config": { "services": [ "bacnet" diff --git a/modules/test/tls/README.md b/modules/test/tls/README.md index f0e0119af..cfca8100c 100644 --- a/modules/test/tls/README.md +++ b/modules/test/tls/README.md @@ -14,9 +14,9 @@ Within the ```python/src``` directory, the below tests are executed. | ID | Description | Expected behavior | Required result |---|---|---|---| -| security.tls.v1_0_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.0 and support | Informational | +| security.tls.v1_0_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.2 and support | Required if Applicable | | security.tls.v1_2_server | Check the device web server TLS 1.2 and the certificate is valid | TLS 1.2 certificate is issues to the web browser client when accessed | Required if Applicable | | security.tls.v1_2_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS v1.2 and support for ECDH and ECDSA ciphers | Required if Applicable | | security.tls.v1_3_server | Check the device web server TLS 1.3 and the certificate is valid | TLS 1.3 certificate is issued to the web browser client when accessed | Informational | -| security.tls.v1_3_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.3 | Informational | +| security.tls.v1_3_client | Device uses TLS with connection to an external service on port 443 (or any other port which could be running the webserver-HTTPS) | The packet indicates a TLS connection with at least TLS 1.3 using AEAD-based ciphers | Informational | diff --git a/modules/test/tls/bin/get_ciphers.sh b/modules/test/tls/bin/get_ciphers.sh index 053afd37f..b05a70cc3 100755 --- a/modules/test/tls/bin/get_ciphers.sh +++ b/modules/test/tls/bin/get_ciphers.sh @@ -18,7 +18,7 @@ CAPTURE_FILE="$1" DST_IP="$2" DST_PORT="$3" -TSHARK_FILTER="ssl.handshake.ciphersuites and ip.dst==$DST_IP and tcp.dstport==$DST_PORT" +TSHARK_FILTER="ssl.handshake.ciphersuites and ip.dst==$DST_IP" response=$(tshark -r "$CAPTURE_FILE" -Y "$TSHARK_FILTER" -Vx | grep 'Cipher Suite:' | awk '{$1=$1};1' | sed 's/Cipher Suite: //') echo "$response" diff --git a/modules/test/tls/bin/get_client_hello_packets.sh b/modules/test/tls/bin/get_client_hello_packets.sh index fd06a3d28..d6075d48f 100755 --- a/modules/test/tls/bin/get_client_hello_packets.sh +++ b/modules/test/tls/bin/get_client_hello_packets.sh @@ -26,7 +26,7 @@ if [[ $TLS_VERSION == '1.0' ]]; then elif [[ $TLS_VERSION == '1.1' ]]; then TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0302" elif [[ $TLS_VERSION == '1.2' || -z $TLS_VERSION ]]; then - TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303" + TSHARK_FILTER="$TSHARK_FILTER and ssl.handshake.version==0x0303 and !tls.handshake.extensions.supported_version==0x0304" elif [[ $TLS_VERSION == '1.3' ]]; then TSHARK_FILTER="$TSHARK_FILTER and (ssl.handshake.version==0x0304 or tls.handshake.extensions.supported_version==0x0304)" else diff --git a/modules/test/tls/python/requirements.txt b/modules/test/tls/python/requirements.txt index 47bab2436..9d0578d6e 100644 --- a/modules/test/tls/python/requirements.txt +++ b/modules/test/tls/python/requirements.txt @@ -3,19 +3,19 @@ # packages to prevent auto-upgrades of stable dependencies appdirs==1.4.4 certifi==2024.8.30 -cffi==1.17.1 +cffi==2.0.0 charset-normalizer==3.3.2 idna==3.8 packaging==24.1 pycparser==2.22 pyshark==0.6 termcolor==2.4.0 -urllib3==2.6.0 +urllib3==2.6.3 # User defined packages -cryptography==44.0.1 -pyOpenSSL==24.3.0 +cryptography==46.0.6 +pyOpenSSL==25.3.0 lxml==5.1.0 # Requirement of pyshark but if upgraded automatically above 5.1 will cause a -requests==2.32.5 +requests==2.33.0 python-nmap==0.7.1 -requests==2.32.5 + diff --git a/modules/test/tls/python/src/tls_module.py b/modules/test/tls/python/src/tls_module.py index f20e4686c..e0cf9f0d3 100644 --- a/modules/test/tls/python/src/tls_module.py +++ b/modules/test/tls/python/src/tls_module.py @@ -454,7 +454,6 @@ def _security_tls_v1_2_server(self): else: LOGGER.error('Could not resolve device IP address. Skipping') description = 'Could not resolve device IP address' - details.append('Could not resolve device IP address.') return 'Error', description, details def _security_tls_v1_3_server(self): @@ -508,38 +507,38 @@ def _security_tls_v1_3_server(self): def _security_tls_v1_0_client(self): LOGGER.info('Running security.tls.v1_0_client') - tls_1_0_valid = self._validate_tls_client(self._device_mac, '1.0') - tls_1_1_valid = self._validate_tls_client(self._device_mac, '1.1') - tls_1_2_valid = self._validate_tls_client(self._device_mac, '1.2') - tls_1_3_valid = self._validate_tls_client(self._device_mac, '1.3') - states = [ - tls_1_0_valid[0], tls_1_1_valid[0], tls_1_2_valid[0], tls_1_3_valid[0] - ] - if any(state is True for state in states): - # If any state is True, return True - result_state = True - result_message = 'TLS 1.0 or higher detected' - elif all(state == 'Feature Not Detected' for state in states): - # If all states are "Feature not Detected" - result_state = 'Feature Not Detected' - result_message = tls_1_0_valid[1] - elif all(state == 'Error' for state in states): - # If all states are "Error" - result_state = 'Error' - result_message = '' - else: + try: + tls_versions = self._tls_util.detect_tls_client_versions( + client_mac=self._device_mac, + capture_files=[ + MONITOR_CAPTURE_FILE, STARTUP_CAPTURE_FILE, TLS_CAPTURE_FILE + ] + ) + LOGGER.info(f'TLS client version detection results: {tls_versions}') + except Exception as e: + LOGGER.error(f'Error detecting TLS client versions: {e}') + return 'Error', f'Error detecting TLS client versions: {e}', [] + result_state = 'Feature Not Detected' + result_message = 'No TLS client connections detected.' + result_details = [] + + if tls_versions['1.0']['present'] or tls_versions['1.1']['present']: result_state = False - result_message = 'TLS 1.0 or higher was not detected' - result_details = [ - *tls_1_0_valid[2], - *tls_1_1_valid[2], - *tls_1_2_valid[2], - *tls_1_3_valid[2] - ] - result_tags = list( - set(tls_1_0_valid[3] + tls_1_1_valid[3] + tls_1_2_valid[3] + - tls_1_3_valid[3])) - return result_state, result_message, result_details, result_tags + result_message = 'TLS 1.0 or TLS 1.1 detected.' + LOGGER.info(result_message) + result_details.extend(tls_versions['1.0']['details']) + result_details.extend(tls_versions['1.1']['details']) + for detail in result_details: + LOGGER.info(detail) + elif tls_versions['1.2']['present'] or tls_versions['1.3']['present']: + result_state = True + result_message = 'TLS 1.2 or higher detected.' + LOGGER.info(result_message) + result_details.extend(tls_versions['1.2']['details']) + result_details.extend(tls_versions['1.3']['details']) + for detail in result_details: + LOGGER.info(detail) + return result_state, result_message, result_details def _security_tls_v1_2_client(self): LOGGER.info('Running security.tls.v1_2_client') diff --git a/modules/test/tls/python/src/tls_util.py b/modules/test/tls/python/src/tls_util.py index 1f4f502f6..1bf5051dc 100644 --- a/modules/test/tls/python/src/tls_util.py +++ b/modules/test/tls/python/src/tls_util.py @@ -148,22 +148,27 @@ def get_public_certificate(self, cert_pem = ssl.DER_cert_to_PEM_cert(secure_sock.getpeercert(True)) except ConnectionRefusedError: - LOGGER.info(f'Connection to {host}:{port} was refused.') - return None + error_msg = f'Connection to {host}:{port} was refused.' + LOGGER.info(error_msg) + return None, error_msg except socket.gaierror: - LOGGER.info(f'Failed to resolve the hostname {host}.') - return None + error_msg = f'Failed to resolve the hostname {host}.' + LOGGER.info(error_msg) + return None, error_msg except ssl.SSLError as e: - LOGGER.info(f'SSL error occurred: {e}') - return None + error_msg = f'SSL error occurred: {e}' + LOGGER.info(error_msg) + return None, error_msg except socket.timeout: - LOGGER.info('Socket timeout error') - return None + error_msg = 'Socket timeout error' + LOGGER.info(error_msg) + return None, error_msg except OSError as e: - LOGGER.error(e) - return None + error_msg = e + LOGGER.info(error_msg) + return None, error_msg - return cert_pem + return cert_pem, None def get_public_key(self, public_cert): # Extract and return the public key from the certificate @@ -336,7 +341,7 @@ def validate_trusted_ca_signature(self, host, port): # within the valid CA root certs stored on the server LOGGER.info( 'Checking for valid signature from authorized Certificate Authorities') - public_cert = self.get_public_certificate(host=host, + public_cert, _ = self.get_public_certificate(host=host, port=port, validate_cert=True, tls_version='1.2') @@ -505,7 +510,7 @@ def validate_tls_server(self, tls_version: str, port: int=443 ) -> tuple[bool| None, list| str]: - cert_pem = self.get_public_certificate(host=host, + cert_pem, error_reason = self.get_public_certificate(host=host, port=port, validate_cert=False, tls_version=tls_version) @@ -540,8 +545,10 @@ def validate_tls_server(self, LOGGER.info('Certificate validated: ' + str(cert_valid)) return cert_valid, details else: - LOGGER.info('Failed to resolve public certificate') - return None, ['Failed to resolve public certificate'] + final_msg = error_reason \ + or f'No TLS {tls_version} server functionality found' + LOGGER.info(final_msg) + return None, [final_msg] def write_cert_to_file(self, cert_name, cert): try: @@ -568,9 +575,9 @@ def write_cert_to_file(self, cert_name, cert): LOGGER.error(f'Failed to write certificate to file: {e}') return None - def get_ciphers(self, capture_file, dst_ip, dst_port): + def get_ciphers(self, capture_file, dst_ip): bin_file = self._bin_dir + '/get_ciphers.sh' - args = f'"{capture_file}" {dst_ip} {dst_port}' + args = f'"{capture_file}" {dst_ip}' command = f'{bin_file} {args}' response = util.run_command(command) ciphers = response[0].split('\n') @@ -586,7 +593,11 @@ def get_hello_packets(self, capture_files, src_mac, tls_version): packets = response[0].strip() if len(packets) > 0: # Parse each packet and append key-value pairs to combined_results - result = self.parse_packets(json.loads(packets), capture_file) + result = self.parse_packets( + json.loads(packets), + capture_file, + tls_version + ) combined_results.extend(result) return combined_results @@ -645,11 +656,11 @@ def get_tls_packets(self, capture_files, src_mac, tls_version): packets = response[0].strip() parsed_json = json.loads(packets) # Parse each packet and append key-value pairs to combined_results - result = self.parse_packets(parsed_json, capture_file) + result = self.parse_packets(parsed_json, capture_file, tls_version) combined_results.extend(result) return combined_results - def parse_packets(self, packets, capture_file): + def parse_packets(self, packets, capture_file, tls_version): hello_packets = [] for packet in packets: # Extract all the basic IP information about the packet @@ -660,9 +671,11 @@ def parse_packets(self, packets, capture_file): 0] if 'tcp.dstport' in packet_layers else '' # Resolve the ciphers used in this packet and validate expected ones exist - ciphers = self.get_ciphers(capture_file, dst_ip, dst_port) - cipher_support = self.is_ecdh_and_ecdsa(ciphers) - + ciphers = self.get_ciphers(capture_file, dst_ip) + if tls_version == '1.3': + cipher_support = self.has_tls13_cipher(ciphers) + else: + cipher_support = self.is_ecdh_and_ecdsa(ciphers) # Put result together hello_packet = {} hello_packet['dst_ip'] = dst_ip @@ -673,43 +686,53 @@ def parse_packets(self, packets, capture_file): hello_packets.append(hello_packet) return hello_packets + def _validate_packet_ciphers(self, packet: dict, tls_version: str) -> bool: + if tls_version == '1.2': + return (packet['cipher_support']['ecdh'] and + packet['cipher_support']['ecdsa']) + elif tls_version == '1.3': + return any(packet['cipher_support'].values()) + else: + return False + def process_hello_packets(self, hello_packets, allowed_protocol_client_ips, tls_version='1.2'): # Validate the ciphers only for tls 1.2 client_hello_results = {'valid': [], 'invalid': []} - - if tls_version == '1.2': + if tls_version in ('1.2', '1.3'): for packet in hello_packets: - if packet['dst_ip'] not in str(client_hello_results['valid']): - LOGGER.info('Checking client ciphers: ' + str(packet)) - if packet['cipher_support']['ecdh'] and packet['cipher_support'][ - 'ecdsa']: + dst_ip = packet['dst_ip'] + if dst_ip not in str(client_hello_results['valid']): + LOGGER.info(f'Checking client ciphers TLS{tls_version}: {packet}') + if self._validate_packet_ciphers(packet, tls_version): LOGGER.info('Required ciphers detected') client_hello_results['valid'].append(packet) # If a previous hello packet to the same destination failed, # we can now remove it as it has passed on a different attempt - if packet['dst_ip'] in str(client_hello_results['invalid']): + if dst_ip in str(client_hello_results['invalid']): LOGGER.info(str(client_hello_results['invalid'])) for invalid_packet in client_hello_results['invalid']: - if packet['dst_ip'] in str(invalid_packet): + if dst_ip in str(invalid_packet): client_hello_results['invalid'].remove(invalid_packet) else: LOGGER.info('Required ciphers not detected') - if packet['dst_ip'] not in allowed_protocol_client_ips: - if packet['dst_ip'] not in str(client_hello_results['invalid']): + if dst_ip not in allowed_protocol_client_ips: + if dst_ip not in str(client_hello_results['invalid']): client_hello_results['invalid'].append(packet) else: - LOGGER.info( - 'Allowing protocol connection, cipher check failure ignored.') - protocol_name = allowed_protocol_client_ips[packet['dst_ip']] + LOGGER.info('''Allowing protocol connection, + cipher check failure ignored.''') + protocol_name = allowed_protocol_client_ips[dst_ip] packet['protocol_details'] = ( - f'\nAllowing {protocol_name} traffic to {packet["dst_ip"]}') # pylint: disable=W1405 + f'\nAllowing {protocol_name} traffic to {dst_ip}') client_hello_results['valid'].append(packet) else: - # No cipher check for TLS 1.0, 1.1 or TLS 1.3 - client_hello_results['valid'] = hello_packets + # No cipher check for TLS 1.0, 1.1 + if tls_version not in ('1.2', '1.3'): + client_hello_results['valid'] = hello_packets + return client_hello_results # Check if the device has made any outbound connections that don't @@ -932,6 +955,43 @@ def validate_tls_client(self, connection detected to {ip}\n''' return tls_client_valid, tls_client_details + def detect_tls_client_versions(self, + client_mac: str, + capture_files: list[str], + version_list: list[str] | None = None + ) -> dict: + """Detect all TLS client versions from packet captures.""" + if version_list is None: + version_list = ['1.0', '1.1', '1.2', '1.3'] + + version_results = {} + for tls_version in version_list: + tls_packets = self.get_tls_packets(capture_files, client_mac, + tls_version) + tls_present = False + details = [] + unique_packets = self._get_unique_packets(tls_packets) + for packet in unique_packets: + dst_ip = packet['dst_ip'] + details.append(f'TLS {tls_version} packet detected to IP: {dst_ip}') + if tls_packets: + tls_present = True + version_results[tls_version] = { + 'present': tls_present, + 'details': details + } + return version_results + + def _get_unique_packets(self, packets: list[dict]) -> list[dict]: + unique_packets = [] + seen_dst_ips = set() + for packet in packets: + dst_ip = packet['dst_ip'] + if dst_ip not in seen_dst_ips: + unique_packets.append(packet) + seen_dst_ips.add(dst_ip) + return unique_packets + def is_ecdh_and_ecdsa(self, ciphers): ecdh = False ecdsa = False @@ -939,3 +999,26 @@ def is_ecdh_and_ecdsa(self, ciphers): ecdh |= 'ECDH' in cipher ecdsa |= 'ECDSA' in cipher return {'ecdh': ecdh, 'ecdsa': ecdsa} + + + def has_tls13_cipher(self, ciphers: list[str]) -> dict[str, bool]: + """ + Check if any of the ciphers in the list are TLS 1.3 ciphers. + """ + tls_13_ciphers = { + 'TLS_AES_128_GCM_SHA256', + 'TLS_AES_256_GCM_SHA384', + 'TLS_CHACHA20_POLY1305_SHA256' + } + valid_ciphers = {} + for cipher in ciphers: + name = cipher.split(' (')[0].strip() + if name in tls_13_ciphers: + valid_ciphers[name] = True + tls_13_ciphers.remove(name) + if len(tls_13_ciphers) == 0: + break + if len(tls_13_ciphers) > 0: + for cipher in tls_13_ciphers: + valid_ciphers[cipher] = False + return valid_ciphers diff --git a/modules/ui/src/app/app.component.spec.ts b/modules/ui/src/app/app.component.spec.ts index 7c785743f..146130188 100644 --- a/modules/ui/src/app/app.component.spec.ts +++ b/modules/ui/src/app/app.component.spec.ts @@ -77,6 +77,7 @@ import { TestingCompleteComponent } from './components/testing-complete/testing- import { VersionComponent } from './components/version/version.component'; import { MOCK_MODULES } from './mocks/device.mock'; import { HelpTips } from './model/tip-config'; +import { MOCK_INFO } from './mocks/topic.mock'; const windowMock = { location: { @@ -117,14 +118,14 @@ describe('AppComponent', () => { 'getTestModules', 'testrunInProgress', 'fetchProfiles', - 'getHistory', + 'getReports', ]); mockFocusManagerService = jasmine.createSpyObj('mockFocusManagerService', [ 'focusFirstElementInContainer', ]); mockLiveAnnouncer = jasmine.createSpyObj('mockLiveAnnouncer', ['announce']); - mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters', 'getInfo']); TestBed.configureTestingModule({ imports: [ @@ -203,6 +204,7 @@ describe('AppComponent', () => { mockService.fetchDevices.and.returnValue(of([])); mockService.getTestModules.and.returnValue(of([...MOCK_MODULES])); mockMqttService.getNetworkAdapters.and.returnValue(of(MOCK_ADAPTERS)); + mockMqttService.getInfo.and.returnValue(of(MOCK_INFO)); store = TestBed.inject(MockStore); fixture = TestBed.createComponent(AppComponent); component = fixture.componentInstance; diff --git a/modules/ui/src/app/app.component.ts b/modules/ui/src/app/app.component.ts index 6aec0a277..7116aaa51 100644 --- a/modules/ui/src/app/app.component.ts +++ b/modules/ui/src/app/app.component.ts @@ -213,6 +213,7 @@ export class AppComponent implements AfterViewInit { this.appStore.getNetworkAdapters(); this.appStore.getInterfaces(); this.appStore.getSystemConfig(); + this.appStore.getInfo(); this.matIconRegistry.addSvgIcon( 'device_run', this.domSanitizer.bypassSecurityTrustResourceUrl(DEVICES_RUN_URL) diff --git a/modules/ui/src/app/app.store.spec.ts b/modules/ui/src/app/app.store.spec.ts index efd238325..4c89c8d95 100644 --- a/modules/ui/src/app/app.store.spec.ts +++ b/modules/ui/src/app/app.store.spec.ts @@ -56,6 +56,7 @@ import { TestRunMqttService } from './services/test-run-mqtt.service'; import { MOCK_ADAPTERS } from './mocks/settings.mock'; import { TestingType } from './model/device'; import { ResultOfTestrun, StatusOfTestrun } from './model/testrun-status'; +import { MOCK_INFO } from './mocks/topic.mock'; const mock = (() => { let store: { [key: string]: string } = {}; @@ -109,7 +110,7 @@ describe('AppStore', () => { mockFocusManagerService = jasmine.createSpyObj([ 'focusFirstElementInContainer', ]); - mockMqttService = jasmine.createSpyObj(['getNetworkAdapters']); + mockMqttService = jasmine.createSpyObj(['getNetworkAdapters', 'getInfo']); TestBed.configureTestingModule({ providers: [ @@ -341,6 +342,20 @@ describe('AppStore', () => { }); }); + describe('getInfo', () => { + const info = MOCK_INFO; + + beforeEach(() => { + mockMqttService.getInfo.and.returnValue(of(info)); + }); + + it('should notify about new info', () => { + appStore.getInfo(); + + expect(mockNotificationService.notify).toHaveBeenCalledWith('message'); + }); + }); + describe('setCloseCallout', () => { it('should update store', done => { appStore.viewModel$.pipe(skip(1), take(1)).subscribe(store => { @@ -520,7 +535,7 @@ describe('AppStore', () => { }, started: '2023-06-22T09:20:00.123Z', finished: '2023-06-22T09:26:00.123Z', - report: 'https://api.testrun.io/report.pdf', + report: '/report/123', export: 'https://api.testrun.io/export.pdf', tags: [], tests: { diff --git a/modules/ui/src/app/app.store.ts b/modules/ui/src/app/app.store.ts index 6ef18b791..d2d3211b1 100644 --- a/modules/ui/src/app/app.store.ts +++ b/modules/ui/src/app/app.store.ts @@ -71,6 +71,7 @@ import { map } from 'rxjs/internal/operators/map'; import { TestrunDialogService } from './services/testrun-dialog.service'; import { Routes } from './model/routes'; import { Router } from '@angular/router'; +import { Info } from './model/topic'; export const CONSENT_SHOWN_KEY = 'CONSENT_SHOWN'; export const CALLOUT_STATE_KEY = 'CALLOUT_STATE'; @@ -253,6 +254,18 @@ export class AppStore extends ComponentStore { ); }); + getInfo = this.effect(trigger$ => { + return trigger$.pipe( + exhaustMap(() => { + return this.testRunMqttService.getInfo().pipe( + tap((info: Info) => { + this.notificationService.notify(info.message); + }) + ); + }) + ); + }); + private notifyAboutTheAdapters(adapters: SystemInterfaces) { this.notificationService.notify( `New network adapter(s) ${Object.keys(adapters).join(', ')} has been detected. You can switch to using it in the System settings menu` diff --git a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.html b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.html index 48212b84a..f2892fe07 100644 --- a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.html +++ b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.html @@ -17,7 +17,7 @@ diff --git a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.spec.ts b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.spec.ts index 01e5a09f8..5fc3493b4 100644 --- a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.spec.ts +++ b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.spec.ts @@ -76,9 +76,7 @@ describe('DownloadReportComponent', () => { ) as HTMLAnchorElement; expect(downloadReportLink).not.toBeNull(); - expect(downloadReportLink.href).toEqual( - 'https://api.testrun.io/report.pdf' - ); + expect(downloadReportLink.href).toContain('/report/123'); expect(downloadReportLink.download).toEqual( 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20' ); diff --git a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts index a1b09d5c1..8e2ac9959 100644 --- a/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts +++ b/modules/ui/src/app/components/download-report-pdf/download-report-pdf.component.ts @@ -21,7 +21,6 @@ import { ReportActionComponent } from '../report-action/report-action.component' @Component({ selector: 'app-download-report-pdf', templateUrl: './download-report-pdf.component.html', - imports: [DownloadReportComponent], providers: [DatePipe], changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/modules/ui/src/app/components/download-report/download-report.component.spec.ts b/modules/ui/src/app/components/download-report/download-report.component.spec.ts index af711a26b..cc41dc434 100644 --- a/modules/ui/src/app/components/download-report/download-report.component.spec.ts +++ b/modules/ui/src/app/components/download-report/download-report.component.spec.ts @@ -136,9 +136,7 @@ describe('DownloadReportComponent', () => { ) as HTMLAnchorElement; expect(downloadReportLink).not.toBeNull(); - expect(downloadReportLink.href).toEqual( - 'https://api.testrun.io/report.pdf' - ); + expect(downloadReportLink.href).toContain('/report/123'); expect(downloadReportLink.download).toEqual( 'delta_03-din-cpu_1.2.2_compliant_22_jun_2023_9:20' ); diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts index 2ee9bd999..0b111010e 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.spec.ts @@ -35,6 +35,7 @@ describe('DownloadZipModalComponent', () => { const testRunServiceMock = jasmine.createSpyObj('testRunServiceMock', [ 'getRiskClass', 'downloadZip', + 'getReportLink', ]); const focusServiceMock: jasmine.SpyObj = jasmine.createSpyObj('focusServiceMock', ['focusFirstElementInContainer']); diff --git a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts index a88eabe0a..11800b374 100644 --- a/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts +++ b/modules/ui/src/app/components/download-zip-modal/download-zip-modal.component.ts @@ -185,6 +185,8 @@ export class DownloadZipModalComponent } private getZipLink(data: DialogData): string { - return data.export || data.report!.replace('report', 'export'); + return this.testRunService.getReportLink( + data.export || data.report!.replace('report', 'export') + ); } } diff --git a/modules/ui/src/app/components/report-action/report-action.component.ts b/modules/ui/src/app/components/report-action/report-action.component.ts index 5607a67f0..8191af453 100644 --- a/modules/ui/src/app/components/report-action/report-action.component.ts +++ b/modules/ui/src/app/components/report-action/report-action.component.ts @@ -6,6 +6,7 @@ import { } from '@angular/core'; import { DatePipe } from '@angular/common'; import { TestrunStatus } from '../../model/testrun-status'; +import { TestRunService } from '../../services/test-run.service'; @Component({ selector: 'app-report-action', @@ -17,6 +18,7 @@ import { TestrunStatus } from '../../model/testrun-status'; }) export class ReportActionComponent { private datePipe = inject(DatePipe); + private testRunService = inject(TestRunService); @Input() data!: TestrunStatus; @@ -32,4 +34,8 @@ export class ReportActionComponent { getFormattedDateString(date: string | null) { return date ? this.datePipe.transform(date, 'd MMM y H:mm') : ''; } + + getReportUrl(data: TestrunStatus) { + return this.testRunService.getReportLink(data.report); + } } diff --git a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts index 087758dc3..909613b0f 100644 --- a/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts +++ b/modules/ui/src/app/components/testing-complete/testing-complete.component.spec.ts @@ -65,7 +65,7 @@ describe('TestingCompleteComponent', () => { profiles: [], testrunStatus: MOCK_PROGRESS_DATA_COMPLIANT, isTestingComplete: true, - report: 'https://api.testrun.io/report.pdf', + report: '/report/123', export: '', isPilot: false, }, diff --git a/modules/ui/src/app/mocks/reports.mock.ts b/modules/ui/src/app/mocks/reports.mock.ts index 5cfff1e06..af72f5346 100644 --- a/modules/ui/src/app/mocks/reports.mock.ts +++ b/modules/ui/src/app/mocks/reports.mock.ts @@ -1,10 +1,9 @@ -import { HistoryTestrun, TestrunStatus } from '../model/testrun-status'; +import { HistoryTestrun, TestReportsList } from '../model/testrun-status'; import { MatTableDataSource } from '@angular/material/table'; import { DeviceStatus, TestingType } from '../model/device'; export const HISTORY = [ { - mac_addr: '01:02:03:04:05:06', status: 'Complete', result: 'Compliant', device: { @@ -15,16 +14,15 @@ export const HISTORY = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/123', + export: '/export/123', + delete: '/report/123', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, { status: 'Complete', result: 'Compliant', - mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -33,14 +31,13 @@ export const HISTORY = [ firmware: '1.2.3', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/1234', + export: '/export/1234', + delete: '/report/1234', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', }, { - mac_addr: null, status: 'Complete', result: 'Compliant', device: { @@ -51,17 +48,16 @@ export const HISTORY = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/12345', + export: '/export/12345', + delete: '/report/12345', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, -] as TestrunStatus[]; +] as TestReportsList; export const HISTORY_AFTER_REMOVE = [ { - mac_addr: '01:02:03:04:05:06', status: 'Complete', result: 'Compliant', device: { @@ -72,14 +68,13 @@ export const HISTORY_AFTER_REMOVE = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/123', + export: '/export/123', + delete: '/report/123', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, { - mac_addr: null, status: 'Complete', result: 'Compliant', device: { @@ -90,19 +85,18 @@ export const HISTORY_AFTER_REMOVE = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/12345', + export: '/export/12345', + delete: '/report/12345', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', }, -] as TestrunStatus[]; +] as TestReportsList; export const FORMATTED_HISTORY = [ { status: 'Complete', result: 'Compliant', - mac_addr: '01:02:03:04:05:06', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -111,9 +105,9 @@ export const FORMATTED_HISTORY = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/123', + export: '/export/123', + delete: '/report/123', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', @@ -125,7 +119,6 @@ export const FORMATTED_HISTORY = [ { status: 'Complete', result: 'Compliant', - mac_addr: '01:02:03:04:05:07', device: { status: DeviceStatus.VALID, manufacturer: 'Delta', @@ -134,9 +127,9 @@ export const FORMATTED_HISTORY = [ firmware: '1.2.3', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/1234', + export: '/export/1234', + delete: '/report/1234', started: '2023-07-23T10:11:00.123Z', finished: '2023-07-23T10:17:10.123Z', deviceFirmware: '1.2.3', @@ -146,7 +139,6 @@ export const FORMATTED_HISTORY = [ program: 'Device Qualification', }, { - mac_addr: null, status: 'Complete', result: 'Compliant', device: { @@ -157,9 +149,9 @@ export const FORMATTED_HISTORY = [ firmware: '1.2.2', test_pack: TestingType.Qualification, }, - tags: [], - report: 'https://api.testrun.io/report.pdf', - export: 'https://api.testrun.io/export.pdf', + report: '/report/12345', + export: '/export/12345', + delete: '/report/12345', started: '2023-06-23T10:11:00.123Z', finished: '2023-06-23T10:17:10.123Z', deviceFirmware: '1.2.2', diff --git a/modules/ui/src/app/mocks/testrun.mock.ts b/modules/ui/src/app/mocks/testrun.mock.ts index 023dfb300..8ebab02dc 100644 --- a/modules/ui/src/app/mocks/testrun.mock.ts +++ b/modules/ui/src/app/mocks/testrun.mock.ts @@ -131,7 +131,7 @@ export const MOCK_PROGRESS_DATA_COMPLIANT: TestrunStatus = StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA, - 'https://api.testrun.io/report.pdf', + '/report/123', ResultOfTestrun.Compliant ); @@ -140,7 +140,7 @@ export const MOCK_PROGRESS_DATA_NON_COMPLIANT: TestrunStatus = StatusOfTestrun.Complete, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf', + '/report/123', ResultOfTestrun.NonCompliant ); @@ -148,7 +148,7 @@ export const MOCK_PROGRESS_DATA_PROCEED: TestrunStatus = PROGRESS_DATA_RESPONSE( StatusOfTestrun.Proceed, '2023-06-22T09:20:00.123Z', TEST_DATA_RESULT, - 'https://api.testrun.io/report.pdf', + '/report/123', ResultOfTestrun.Compliant ); diff --git a/modules/ui/src/app/mocks/topic.mock.ts b/modules/ui/src/app/mocks/topic.mock.ts index 4309ae84f..382f56705 100644 --- a/modules/ui/src/app/mocks/topic.mock.ts +++ b/modules/ui/src/app/mocks/topic.mock.ts @@ -1,5 +1,9 @@ -import { InternetConnection } from '../model/topic'; +import { Info, InternetConnection } from '../model/topic'; export const MOCK_INTERNET: InternetConnection = { connection: false, }; + +export const MOCK_INFO: Info = { + message: 'message', +}; diff --git a/modules/ui/src/app/model/certificate.ts b/modules/ui/src/app/model/certificate.ts index 0a36e1300..c35a78bf2 100644 --- a/modules/ui/src/app/model/certificate.ts +++ b/modules/ui/src/app/model/certificate.ts @@ -19,9 +19,15 @@ export interface Certificate { organisation?: string; expires?: string; uploading?: boolean; + type?: CertificateType; } export enum CertificateStatus { Valid = 'Valid', Expired = 'Expired', } + +export enum CertificateType { + Root = 'root', + Intermediate = 'intermediate', +} diff --git a/modules/ui/src/app/model/testrun-status.ts b/modules/ui/src/app/model/testrun-status.ts index af681fe0f..4559f25ac 100644 --- a/modules/ui/src/app/model/testrun-status.ts +++ b/modules/ui/src/app/model/testrun-status.ts @@ -15,6 +15,25 @@ */ import { Device } from './device'; +interface TestRun { + version: string; +} + +export interface TestrunReport { + testrun: TestRun; + device: IDevice; + status: StatusOfTestrun; + result: ResultOfTestrun; + started: string; + finished: string; + report: string; + export: string; + folder_name: string; + delete: string; +} + +export type TestReportsList = TestrunReport[]; + export interface TestrunStatus { mac_addr: string | null; status: StatusOfTestrun; @@ -29,7 +48,7 @@ export interface TestrunStatus { tags: string[] | null; } -export interface HistoryTestrun extends TestrunStatus { +export interface HistoryTestrun extends TestrunReport { deviceFirmware: string; deviceInfo: string; testResult: string; @@ -125,8 +144,3 @@ export const IDLE_STATUS = { }, tags: [], } as TestrunStatus; - -export type TestrunStatusKey = keyof typeof StatusOfTestrun; -export type TestrunStatusValue = (typeof StatusOfTestrun)[TestrunStatusKey]; -export type TestResultKey = keyof typeof StatusOfTestResult; -export type TestResultValue = (typeof StatusOfTestResult)[TestResultKey]; diff --git a/modules/ui/src/app/model/topic.ts b/modules/ui/src/app/model/topic.ts index d330dbb82..701153135 100644 --- a/modules/ui/src/app/model/topic.ts +++ b/modules/ui/src/app/model/topic.ts @@ -2,8 +2,13 @@ export enum Topic { NetworkAdapters = 'events/adapter', InternetConnection = 'events/internet', Status = 'status', + Info = 'info', } export interface InternetConnection { connection: boolean | null; } + +export interface Info { + message: string; +} diff --git a/modules/ui/src/app/pages/certificates/certificates.store.ts b/modules/ui/src/app/pages/certificates/certificates.store.ts index 9d14a014b..67af96a9d 100644 --- a/modules/ui/src/app/pages/certificates/certificates.store.ts +++ b/modules/ui/src/app/pages/certificates/certificates.store.ts @@ -40,7 +40,14 @@ export const CertificatesStore = signalStore( withState({ certificates: [] as Certificate[], selectedCertificate: '', - displayedColumns: ['name', 'organisation', 'expires', 'status', 'actions'], + displayedColumns: [ + 'name', + 'organisation', + 'expires', + 'status', + 'type', + 'actions', + ], dataLoaded: false, }), withComputed(({ certificates }) => ({ diff --git a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html index eb63ee8b3..108e3a2dd 100644 --- a/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html +++ b/modules/ui/src/app/pages/certificates/components/certificates-table/certificates-table.component.html @@ -56,7 +56,7 @@ *matHeaderCellDef mat-header-cell mat-sort-header - sortActionDescription="Sort by duration time"> + sortActionDescription="Sort by status"> Status @@ -65,6 +65,22 @@ + + + Type + + + + {{ data.type }} + + + Reports @if (data?.device) { + (removeDevice)="removeReport(data)"> diff --git a/modules/ui/src/app/pages/reports/reports.component.spec.ts b/modules/ui/src/app/pages/reports/reports.component.spec.ts index a9f444d21..d95d381fc 100644 --- a/modules/ui/src/app/pages/reports/reports.component.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.component.spec.ts @@ -77,7 +77,7 @@ describe('ReportsComponent', () => { }); }; beforeEach(() => { - mockService = jasmine.createSpyObj(['getResultClass']); + mockService = jasmine.createSpyObj(['getResultClass', 'getReportLink']); mockReportsStore = jasmine.createSpyObj('ReportsStore', [ 'deleteReport', 'setSelectedRow', @@ -89,8 +89,9 @@ describe('ReportsComponent', () => { 'setActiveFiler', 'setFilterOpened', 'updateSort', - 'getHistory', 'getReports', + 'getReports', + 'fetchReports', ]); mockLiveAnnouncer = jasmine.createSpyObj(['announce']); @@ -118,7 +119,7 @@ describe('ReportsComponent', () => { it('should get reports', fakeAsync(() => { component.ngOnInit(); - expect(mockReportsStore.getReports).toHaveBeenCalled(); + expect(mockReportsStore.fetchReports).toHaveBeenCalled(); })); }); @@ -294,12 +295,8 @@ describe('ReportsComponent', () => { it('#removeDevice should call delete report', () => { const data = HISTORY[0]; - component.removeDevice(data); - expect(mockReportsStore.deleteReport).toHaveBeenCalledWith({ - mac_addr: data.mac_addr, - deviceMacAddr: data.device.mac_addr, - started: data.started, - }); + component.removeReport(data); + expect(mockReportsStore.deleteReport).toHaveBeenCalledWith('/report/123'); }); }); diff --git a/modules/ui/src/app/pages/reports/reports.component.ts b/modules/ui/src/app/pages/reports/reports.component.ts index 356212aaf..a530f97f1 100644 --- a/modules/ui/src/app/pages/reports/reports.component.ts +++ b/modules/ui/src/app/pages/reports/reports.component.ts @@ -24,8 +24,9 @@ import { import { LiveAnnouncer } from '@angular/cdk/a11y'; import { TestRunService } from '../../services/test-run.service'; import { + HistoryTestrun, StatusResultClassName, - TestrunStatus, + TestrunReport, } from '../../model/testrun-status'; import { CommonModule, DatePipe } from '@angular/common'; import { MatSort, MatSortModule, Sort } from '@angular/material/sort'; @@ -85,7 +86,7 @@ export class ReportsComponent implements OnInit, OnDestroy { viewModel$ = this.store.viewModel$; ngOnInit() { - this.store.getReports(); + this.store.fetchReports(); const sort = this.sort(); if (sort) { this.store.updateSort(sort); @@ -183,7 +184,7 @@ export class ReportsComponent implements OnInit, OnDestroy { this.store.setSelectedRow(row); } - trackByStarted(index: number, item: TestrunStatus) { + trackByStarted(index: number, item: HistoryTestrun) { return item.started; } @@ -207,12 +208,8 @@ export class ReportsComponent implements OnInit, OnDestroy { } } - removeDevice(data: TestrunStatus) { - this.store.deleteReport({ - mac_addr: data.mac_addr, - deviceMacAddr: data.device.mac_addr, - started: data.started, - }); + removeReport(data: TestrunReport) { + this.store.deleteReport(data.delete); this.focusNextButton(); } } diff --git a/modules/ui/src/app/pages/reports/reports.store.spec.ts b/modules/ui/src/app/pages/reports/reports.store.spec.ts index 9aec5e9ff..fc76a45a7 100644 --- a/modules/ui/src/app/pages/reports/reports.store.spec.ts +++ b/modules/ui/src/app/pages/reports/reports.store.spec.ts @@ -161,7 +161,7 @@ describe('ReportsStore', () => { }); describe('effects', () => { - describe('getHistory', () => { + describe('getReports', () => { it('should update store', done => { store.overrideSelector(selectReports, [...HISTORY]); store.refreshState(); @@ -171,7 +171,7 @@ describe('ReportsStore', () => { done(); }); - reportsStore.getHistory(); + reportsStore.getReports(); }); }); @@ -181,11 +181,7 @@ describe('ReportsStore', () => { store.overrideSelector(selectReports, [...HISTORY]); store.refreshState(); - reportsStore.deleteReport({ - mac_addr: '01:02:03:04:05:07', - deviceMacAddr: '01:02:03:04:05:07', - started: '2023-07-23T10:11:00.123Z', - }); + reportsStore.deleteReport('/report/1234'); expect(store.dispatch).toHaveBeenCalledWith( setReports({ reports: HISTORY_AFTER_REMOVE }) @@ -198,11 +194,7 @@ describe('ReportsStore', () => { store.overrideSelector(selectReports, [...HISTORY_AFTER_REMOVE]); store.refreshState(); - reportsStore.deleteReport({ - mac_addr: null, - deviceMacAddr: '01:02:03:04:05:08', - started: '2023-06-23T10:11:00.123Z', - }); + reportsStore.deleteReport('/report/12345'); expect(store.dispatch).toHaveBeenCalledWith( setReports({ reports: [HISTORY_AFTER_REMOVE[0]] }) diff --git a/modules/ui/src/app/pages/reports/reports.store.ts b/modules/ui/src/app/pages/reports/reports.store.ts index 4b53ddaf0..6dce5e217 100644 --- a/modules/ui/src/app/pages/reports/reports.store.ts +++ b/modules/ui/src/app/pages/reports/reports.store.ts @@ -4,6 +4,8 @@ import { MatRow, MatTableDataSource } from '@angular/material/table'; import { HistoryTestrun, StatusOfTestrun, + TestReportsList, + TestrunReport, TestrunStatus, } from '../../model/testrun-status'; import { DateRange, Filters } from '../../model/filters'; @@ -64,7 +66,7 @@ export class ReportsStore extends ComponentStore { profiles: this.profiles$, }); - setDataSource = this.updater((state, reports: TestrunStatus[]) => { + setDataSource = this.updater((state, reports: TestReportsList) => { const data = this.formateData(reports); const dataSource = new MatTableDataSource(data); @@ -121,23 +123,17 @@ export class ReportsStore extends ComponentStore { }; }); - deleteReport = this.effect<{ - mac_addr: string | null; - deviceMacAddr: string; - started: string | null; - }>(trigger$ => { + deleteReport = this.effect(trigger$ => { return trigger$.pipe( - exhaustMap(({ mac_addr, deviceMacAddr, started }) => { - return this.testRunService - .deleteReport(mac_addr || deviceMacAddr, started || '') - .pipe( - withLatestFrom(this.history$), - tap(([remove, current]) => { - if (remove) { - this.removeReport(mac_addr, deviceMacAddr, started, current); - } - }) - ); + exhaustMap(url => { + return this.testRunService.deleteReport(url).pipe( + withLatestFrom(this.history$), + tap(([remove, current]) => { + if (remove) { + this.removeReport(url, current); + } + }) + ); }) ); }); @@ -208,7 +204,7 @@ export class ReportsStore extends ComponentStore { ); }); - getHistory = this.effect(() => { + getReports = this.effect(() => { return this.history$.pipe( withLatestFrom(this.filteredValues$), tap(([reports, filteredValues]) => { @@ -218,7 +214,7 @@ export class ReportsStore extends ComponentStore { ); }); - getReports = this.effect(trigger$ => { + fetchReports = this.effect(trigger$ => { return trigger$.pipe( tap(() => { this.store.dispatch(fetchReports()); @@ -226,20 +222,10 @@ export class ReportsStore extends ComponentStore { ); }); - private removeReport( - mac_addr: string | null, - deviceMacAddr: string, - started: string | null, - current: TestrunStatus[] - ) { + private removeReport(url: string, current: TestReportsList) { const history = [...current]; - const idx = history.findIndex( - report => - report.mac_addr === mac_addr && - report.device.mac_addr === deviceMacAddr && - report.started === started - ); - if (typeof idx === 'number') { + const idx = history.findIndex(report => report.delete === url); + if (idx > -1) { history.splice(idx, 1); this.store.dispatch(setReports({ reports: history })); } @@ -255,7 +241,7 @@ export class ReportsStore extends ComponentStore { this.setIsFiltersEmpty(this.isFiltersEmpty(filteredValues)); } - private formateData(data: TestrunStatus[]): HistoryTestrun[] { + private formateData(data: TestReportsList): HistoryTestrun[] { return data.map(item => { return { ...item, @@ -268,7 +254,7 @@ export class ReportsStore extends ComponentStore { }); } - private getTestResult(item: TestrunStatus): string { + private getTestResult(item: TestrunReport): string { let result = ''; if (item.device.test_pack === TestingType.Qualification) { if ( diff --git a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts index 7ffb12443..3198430da 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.spec.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.spec.ts @@ -6,7 +6,7 @@ import SpyObj = jasmine.SpyObj; import { of } from 'rxjs'; import { MOCK_ADAPTERS } from '../mocks/settings.mock'; import { Topic } from '../model/topic'; -import { MOCK_INTERNET } from '../mocks/topic.mock'; +import { MOCK_INFO, MOCK_INTERNET } from '../mocks/topic.mock'; import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; import { TestRunService } from './test-run.service'; @@ -97,6 +97,26 @@ describe('TestRunMqttService', () => { }); }); + describe('getInfo', () => { + beforeEach(() => { + mockService.observe.and.returnValue(of(getResponse(MOCK_INFO))); + }); + + it('should subscribe the topic', done => { + service.getInfo().subscribe(() => { + expect(mockService.observe).toHaveBeenCalledWith(Topic.Info); + done(); + }); + }); + + it('should return object of type', done => { + service.getInfo().subscribe(res => { + expect(res).toEqual(MOCK_INFO); + done(); + }); + }); + }); + function getResponse(response: Type): IMqttMessage { const enc = new TextEncoder(); const message = enc.encode(JSON.stringify(response)); diff --git a/modules/ui/src/app/services/test-run-mqtt.service.ts b/modules/ui/src/app/services/test-run-mqtt.service.ts index c0d78390d..276c1c6b6 100644 --- a/modules/ui/src/app/services/test-run-mqtt.service.ts +++ b/modules/ui/src/app/services/test-run-mqtt.service.ts @@ -4,7 +4,7 @@ import { catchError, Observable, of } from 'rxjs'; import { map } from 'rxjs/operators'; import { Adapters } from '../model/setting'; import { TestrunStatus } from '../model/testrun-status'; -import { InternetConnection, Topic } from '../model/topic'; +import { Info, InternetConnection, Topic } from '../model/topic'; import { TestRunService } from './test-run.service'; @Injectable({ @@ -32,6 +32,10 @@ export class TestRunMqttService { ); } + getInfo(): Observable { + return this.topic(Topic.Info); + } + private topic(topicName: string): Observable { return this.mqttService.observe(topicName).pipe( map( diff --git a/modules/ui/src/app/services/test-run.service.spec.ts b/modules/ui/src/app/services/test-run.service.spec.ts index 5c77a30ee..ba886d9b1 100644 --- a/modules/ui/src/app/services/test-run.service.spec.ts +++ b/modules/ui/src/app/services/test-run.service.spec.ts @@ -26,6 +26,7 @@ import { MOCK_PROGRESS_DATA_IN_PROGRESS } from '../mocks/testrun.mock'; import { StatusOfTestResult, StatusOfTestrun, + TestReportsList, TestrunStatus, } from '../model/testrun-status'; import { device, DEVICES_FORM, MOCK_MODULES } from '../mocks/device.mock'; @@ -223,26 +224,26 @@ describe('TestRunService', () => { }); }); - describe('getHistory', () => { + describe('getReports', () => { it('should return reports', () => { - let result: TestrunStatus[] | null = null; + let result: TestReportsList | null = null; const reports = [ { status: 'Complete', device: device, - report: 'https://api.testrun.io/report.pdf', + report: '/report/123', started: '2023-06-22T10:11:00.123Z', finished: '2023-06-22T10:17:00.123Z', }, - ] as TestrunStatus[]; + ] as TestReportsList; - service.getHistory().subscribe(res => { + service.getReports().subscribe(res => { expect(res).toEqual(result); }); result = reports; - service.getHistory(); + service.getReports(); const req = httpTestingController.expectOne( 'http://localhost:8000/reports' ); @@ -461,36 +462,24 @@ describe('TestRunService', () => { it('deleteReport should have necessary request data', () => { const apiUrl = 'http://localhost:8000/report'; - service.deleteReport(device.mac_addr, '').subscribe(res => { + service.deleteReport('/report').subscribe(res => { expect(res).toEqual(true); }); const req = httpTestingController.expectOne(apiUrl); expect(req.request.method).toBe('DELETE'); - expect(req.request.body).toEqual( - JSON.stringify({ - mac_addr: device.mac_addr, - timestamp: '', - }) - ); req.flush({}); }); it('deleteReport should return false when error happens', () => { const apiUrl = 'http://localhost:8000/report'; - service.deleteReport(device.mac_addr, '').subscribe(res => { + service.deleteReport('/report').subscribe(res => { expect(res).toEqual(false); }); const req = httpTestingController.expectOne(apiUrl); expect(req.request.method).toBe('DELETE'); - expect(req.request.body).toEqual( - JSON.stringify({ - mac_addr: device.mac_addr, - timestamp: '', - }) - ); req.error(new ErrorEvent('')); }); @@ -704,4 +693,11 @@ describe('TestRunService', () => { req.flush(result); }); }); + + describe('getReportLink', () => { + it('should return link', () => { + const link = service.getReportLink('url'); + expect(link.includes('url')).toBeTrue(); + }); + }); }); diff --git a/modules/ui/src/app/services/test-run.service.ts b/modules/ui/src/app/services/test-run.service.ts index ba5733cc0..789a36c15 100644 --- a/modules/ui/src/app/services/test-run.service.ts +++ b/modules/ui/src/app/services/test-run.service.ts @@ -24,6 +24,7 @@ import { StatusOfTestResult, StatusOfTestrun, StatusResultClassName, + TestReportsList, TestrunStatus, } from '../model/testrun-status'; import { Version } from '../model/version'; @@ -151,6 +152,7 @@ export class TestRunService { catchError(() => of(false)) ); } + deleteDevice(device: Device): Observable { return this.http .delete(`${API_URL}/device`, { @@ -162,8 +164,8 @@ export class TestRunService { ); } - getHistory(): Observable { - return this.http.get(`${API_URL}/reports`).pipe( + getReports(): Observable { + return this.http.get(`${API_URL}/reports`).pipe( map(result => { result.forEach(item => { item.report = this.changeReportURL(item.report); @@ -241,15 +243,15 @@ export class TestRunService { }); } - deleteReport(mac_addr: string, started: string): Observable { - return this.http - .delete(`${API_URL}/report`, { - body: JSON.stringify({ mac_addr, timestamp: started }), - }) - .pipe( - catchError(() => of(false)), - map(res => !!res) - ); + getReportLink(url: string): string { + return `${url}`; + } + + deleteReport(url: string): Observable { + return this.http.delete(`${API_URL}${url}`).pipe( + catchError(() => of(false)), + map(res => !!res) + ); } fetchProfiles(): Observable { diff --git a/modules/ui/src/app/store/actions.ts b/modules/ui/src/app/store/actions.ts index 65fae5fb8..da981b607 100644 --- a/modules/ui/src/app/store/actions.ts +++ b/modules/ui/src/app/store/actions.ts @@ -18,7 +18,7 @@ import { createAction, props } from '@ngrx/store'; import { Adapters, InterfacesValidation, SystemConfig } from '../model/setting'; import { SystemInterfaces } from '../model/setting'; import { Device, TestModule } from '../model/device'; -import { TestrunStatus } from '../model/testrun-status'; +import { TestReportsList, TestrunStatus } from '../model/testrun-status'; import { Profile } from '../model/profile'; export const fetchInterfacesSuccess = createAction( @@ -125,7 +125,7 @@ export const fetchReports = createAction('[Shared] Fetch reports'); export const setReports = createAction( '[Shared] Set Reports', - props<{ reports: TestrunStatus[] }>() + props<{ reports: TestReportsList }>() ); export const setTestModules = createAction( diff --git a/modules/ui/src/app/store/effects.spec.ts b/modules/ui/src/app/store/effects.spec.ts index 952b9dbf6..2b23ae542 100644 --- a/modules/ui/src/app/store/effects.spec.ts +++ b/modules/ui/src/app/store/effects.spec.ts @@ -76,7 +76,7 @@ describe('Effects', () => { 'testrunInProgress', 'stopTestrun', 'fetchProfiles', - 'getHistory', + 'getReports', ]); testRunServiceMock.getSystemInterfaces.and.returnValue(of({})); testRunServiceMock.getSystemConfig.and.returnValue(of({ network: {} })); @@ -85,7 +85,7 @@ describe('Effects', () => { of(MOCK_PROGRESS_DATA_IN_PROGRESS) ); testRunServiceMock.fetchProfiles.and.returnValue(of([])); - testRunServiceMock.getHistory.and.returnValue(of([])); + testRunServiceMock.getReports.and.returnValue(of([])); mockMqttService.getInternetConnection.and.returnValue( of({ connection: false }) ); @@ -358,7 +358,7 @@ describe('Effects', () => { describe('onFetchReports$', () => { it(' should call setReports on success', done => { - testRunServiceMock.getHistory.and.returnValue(of([])); + testRunServiceMock.getReports.and.returnValue(of([])); actions$ = of(actions.fetchReports()); effects.onFetchReports$.subscribe(action => { @@ -372,7 +372,7 @@ describe('Effects', () => { }); it('should call setReports with empty array if null is returned', done => { - testRunServiceMock.getHistory.and.returnValue(of(null)); + testRunServiceMock.getReports.and.returnValue(of(null)); actions$ = of(actions.fetchReports()); effects.onFetchReports$.subscribe(action => { @@ -386,7 +386,7 @@ describe('Effects', () => { }); it('should call setReports with empty array if error happens', done => { - testRunServiceMock.getHistory.and.returnValue( + testRunServiceMock.getReports.and.returnValue( throwError( new HttpErrorResponse({ error: { error: 'error' }, status: 500 }) ) diff --git a/modules/ui/src/app/store/effects.ts b/modules/ui/src/app/store/effects.ts index 278cc1bb0..233e01f4a 100644 --- a/modules/ui/src/app/store/effects.ts +++ b/modules/ui/src/app/store/effects.ts @@ -35,7 +35,7 @@ import { selectIsOpenWaitSnackBar, selectSystemStatus } from './selectors'; import { IDLE_STATUS, StatusOfTestrun, - TestrunStatus, + TestReportsList, } from '../model/testrun-status'; import { fetchSystemStatusSuccess, @@ -253,8 +253,8 @@ export class AppEffects { return this.actions$.pipe( ofType(AppActions.fetchReports), switchMap(() => - this.testrunService.getHistory().pipe( - map((reports: TestrunStatus[] | null) => { + this.testrunService.getReports().pipe( + map((reports: TestReportsList | null) => { if (reports !== null) { return AppActions.setReports({ reports }); } diff --git a/modules/ui/src/app/store/state.ts b/modules/ui/src/app/store/state.ts index 9d5613730..62160da3a 100644 --- a/modules/ui/src/app/store/state.ts +++ b/modules/ui/src/app/store/state.ts @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import { TestrunStatus } from '../model/testrun-status'; +import { TestReportsList, TestrunStatus } from '../model/testrun-status'; import { Device, TestModule } from '../model/device'; import { Adapters, SystemConfig, SystemInterfaces } from '../model/setting'; import { Profile } from '../model/profile'; @@ -42,7 +42,7 @@ export interface AppState { isStopTestrun: boolean; isOpenWaitSnackBar: boolean; deviceInProgress: Device | null; - reports: TestrunStatus[]; + reports: TestReportsList; testModules: TestModule[]; adapters: Adapters; internetConnection: boolean | null; diff --git a/resources/report/test_report_styles.css b/resources/report/test_report_styles.css index d6ebf2552..d6f9aeacb 100644 --- a/resources/report/test_report_styles.css +++ b/resources/report/test_report_styles.css @@ -276,7 +276,7 @@ .steps-to-resolve-test-name { display: inline-block; margin-left: 70px; - margin-right: 10px; + margin-right: -60px; margin-bottom: 20px; width: 250px; vertical-align: top; @@ -284,6 +284,8 @@ .steps-to-resolve-description { display: inline-block; + margin-left: 70px; + margin-bottom: 20px; } .steps-to-resolve.subtitle { diff --git a/test_vm/Vagrantfile b/test_vm/Vagrantfile index 8d01960f4..08e9ef588 100644 --- a/test_vm/Vagrantfile +++ b/test_vm/Vagrantfile @@ -1,6 +1,6 @@ Vagrant.configure("2") do |config| config.vm.box = "cloud-image/ubuntu-24.04" - config.vm.box_version = "20251126.0.0" + config.vm.box_version = "20260307.0.0" config.vm.hostname = "ubuntu-vm" if ENV['DEVICE'] == 'COMPLIANT' config.vm.network "private_network", diff --git a/test_vm/provision-compliant.yml b/test_vm/provision-compliant.yml index 774339cfd..92c6b9a6c 100644 --- a/test_vm/provision-compliant.yml +++ b/test_vm/provision-compliant.yml @@ -39,7 +39,7 @@ git: repo: https://github.com/bacnet-stack/bacnet-stack.git dest: /opt/bacnet-stack - version: master + version: bacnet-stack-1.4.2 - name: Build BACnet stack make: @@ -57,6 +57,7 @@ IP=$(ip -4 addr show $IFACE | awk '/inet / {print $2}' | cut -d/ -f1) if [ -n "$IP" ]; then export BACNET_IP=$IP + export BACNET_PORT=47808 exec /opt/bacnet-stack/bin/bacserv fi sleep 2 @@ -75,7 +76,7 @@ [Service] Type=simple ExecStart=/usr/local/bin/bacserv-wrapper.sh - Restart=always + Restart=on-failure RestartSec=5 [Install] @@ -269,13 +270,10 @@ fi sleep 1 done - # TLS 1.0 - openssl s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind "$IP" - # TLS 1.2 - openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" - # TLS 1.3 - openssl s_client -connect tls13.badssl.com:443 -tls1_3 -bind "$IP" - + # TLS 1.2 (using OpenSSL 3.1.4) + timeout 3 openssl s_client -connect tls-v1-2.badssl.com:1012 -tls1_2 -bind "$IP" -quiet /dev/null || true + # TLS 1.3 (using OpenSSL 3.1.4) + timeout 3 openssl s_client -connect google.com:443 -tls1_3 -bind "$IP" -quiet /dev/null || true - name: Create systemd service for TLS emulation ansible.builtin.copy: dest: /etc/systemd/system/tls-emulate.service diff --git a/test_vm/provision-non-compliant-tls.yml b/test_vm/provision-non-compliant-tls.yml index dc9388202..4dc419c59 100644 --- a/test_vm/provision-non-compliant-tls.yml +++ b/test_vm/provision-non-compliant-tls.yml @@ -12,30 +12,74 @@ ssl_key: /etc/ssl/private/nginx-selfsigned.key tasks: - - name: Ensure openssl, dhclient, and nginx are installed + - name: Ensure openssl, dhclient, nginx and build dependencies are installed apt: name: - openssl - isc-dhcp-client - nginx + - perl + - pkg-config + - zlib1g-dev + - wget + - gcc + - make state: present update_cache: yes + - name: Download OpenSSL 1.1.1u source + get_url: + url: https://www.openssl.org/source/openssl-1.1.1u.tar.gz + dest: /tmp/openssl-1.1.1u.tar.gz + mode: '0644' + force: no + + - name: Extract OpenSSL 1.1.1u source + unarchive: + src: /tmp/openssl-1.1.1u.tar.gz + dest: /tmp + remote_src: yes + creates: /tmp/openssl-1.1.1u + + - name: Build and install OpenSSL 1.1.1 + shell: | + ./config --prefix=/opt/openssl-1.1.1 no-shared + make -j"$(nproc)" + make install_sw + args: + chdir: /tmp/openssl-1.1.1u + creates: /opt/openssl-1.1.1/bin/openssl + - name: Run dhclient on ens5 (in background) shell: nohup dhclient {{ iface }} & async: 0 poll: 0 - # NGINX + - name: Download OpenSSL 3.1.4 source + get_url: + url: https://www.openssl.org/source/openssl-3.1.4.tar.gz + dest: /tmp/openssl-3.1.4.tar.gz + mode: '0644' + force: no + + - name: Extract OpenSSL 3.1.4 source + unarchive: + src: /tmp/openssl-3.1.4.tar.gz + dest: /tmp + remote_src: yes + creates: /tmp/openssl-3.1.4 + + - name: Build and install OpenSSL 3.1.4 + shell: | + ./config --prefix=/opt/openssl-3.1.4 no-shared + make -j"$(nproc)" + make install_sw + args: + chdir: /tmp/openssl-3.1.4 + creates: /opt/openssl-3.1.4/bin/openssl - - name: Ensure openssl, dhclient, and nginx are installed - apt: - name: - - openssl - - isc-dhcp-client - - nginx - state: present - update_cache: yes + + # NGINX - name: Generate self-signed SSL certificate for nginx command: > @@ -86,6 +130,14 @@ state: restarted enabled: yes + - name: Create custom OpenSSL config for legacy protocol support + copy: + dest: /etc/ssl/openssl-legacy.cnf + content: | + .include /etc/ssl/openssl.cnf + [system_default_sect] + MinProtocol = TLSv1.0 + MaxProtocol = TLSv1.3 - name: Copy fail_tls_handshake.sh script copy: @@ -114,9 +166,18 @@ echo "No IP $ip_addr on $iface" >> $LOG exit 1 fi - # TLS 1.2 + export OPENSSL_CONF=/etc/ssl/openssl-legacy.cnf + OLD_OPENSSL=/opt/openssl-1.1.1/bin/openssl + NEW_OPENSSL=/opt/openssl-3.1.4/bin/openssl + echo "Running TLS 1.0 handshake..." >> $LOG + timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-0.badssl.com:1010 -tls1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true + echo "Running TLS 1.1 handshake..." >> $LOG + timeout 3 "$OLD_OPENSSL" s_client -connect tls-v1-1.badssl.com:1011 -tls1_1 -bind $ip_addr -quiet < /dev/null >> $LOG 2>&1 || true echo "Running TLS 1.2 handshake..." >> $LOG - openssl s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1 + "$NEW_OPENSSL" s_client -connect $host:$port -tls1_2 -cipher "CAMELLIA256-SHA256" -bind $ip_addr -ign_eof < /dev/null >> $LOG 2>&1 + echo "Running TLS 1.3 handshake..." >> $LOG + "$NEW_OPENSSL" s_client -connect tls-v1-2.badssl.com:1012 -tls1_3 + - name: Create systemd service for failing TLS handshake copy: diff --git a/test_vm/provision-non-compliant.yml b/test_vm/provision-non-compliant.yml index 978f50eab..be26c0ead 100644 --- a/test_vm/provision-non-compliant.yml +++ b/test_vm/provision-non-compliant.yml @@ -42,6 +42,14 @@ - net-tools - netcat-openbsd - isc-dhcp-client + - vsftpd + - telnetd + - inetutils-inetd + - postfix + - dovecot-pop3d + - dovecot-imapd + - x11vnc + - xvfb state: present # Set up networking on ens5 @@ -51,6 +59,150 @@ async: 0 poll: 0 + # SNMP (UDP/161) + + - name: Kill only netcat processes listening on UDP/161 (универсально) + shell: | + for pid in $(lsof -t -iUDP:161 | awk '/nc|netcat/ {print $2}'); do + kill $pid + done + ignore_errors: yes + + - name: Install SNMP daemon + apt: + name: snmpd + state: present + update_cache: yes + + - name: Ensure minimal snmpd.conf + copy: + dest: /etc/snmp/snmpd.conf + content: | + agentAddress udp:161 + rocommunity public + + - name: Restart snmpd + service: + name: snmpd + state: restarted + enabled: yes + + - name: Wait for SNMP server to listen on UDP/161 + shell: | + for i in {1..20}; do + ss -uln | grep -q ':161 ' && exit 0 + sleep 1 + done + exit 1 + register: snmpd_udp_check + changed_when: false + failed_when: snmpd_udp_check.rc != 0 + + + + # SSH server configuration + + - name: Gather facts + setup: + + - name: Set ens4_ip from Ansible facts + set_fact: + ens4_ip: "{{ ansible_ens4.ipv4.address | default('') }}" + + - name: Fail if ens4_ip is not set + fail: + msg: "ens4_ip is not set! Please check network configuration." + when: ens4_ip == '' + + - name: Debug ens4 IP + debug: + msg: "ens4 IP is {{ ens4_ip }}" + + - name: Stop and disable ssh.socket (prevents auto-start on all interfaces) + service: + name: ssh.socket + state: stopped + enabled: no + ignore_errors: yes + + - name: Ensure only ens4 is in ListenAddress for sshd + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^ListenAddress' + line: "ListenAddress {{ ens4_ip }}" + state: present + insertafter: '^#Port' + + - name: Remove all other ListenAddress lines from sshd_config + lineinfile: + path: /etc/ssh/sshd_config + regexp: '^ListenAddress (?!{{ ens4_ip }})' + state: absent + + - name: Restart sshd + service: + name: ssh + state: restarted + + - name: Create fake SSH banner systemd service for ens5:22 + copy: + dest: /etc/systemd/system/fake-ssh-banner.service + mode: '0644' + content: | + [Unit] + Description=Fake SSH Banner on ens5:22 + After=network-online.target + Wants=network-online.target + + [Service] + Type=simple + ExecStart=/bin/bash -c '\ + while true; do \ + ens5_IP=$(ip -4 addr show ens5 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \ + if [[ -n "$ens5_IP" ]]; then \ + echo "ens5 IP detected: $ens5_IP"; \ + break; \ + fi; \ + echo "Waiting for ens5 IP..."; \ + sleep 2; \ + done; \ + while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ens5_IP -p 22; done' + Restart=always + + [Install] + WantedBy=multi-user.target + + - name: Reload systemd + systemd: + daemon_reload: yes + + - name: Restart fake-ssh-banner service + systemd: + name: fake-ssh-banner.service + state: restarted + enabled: yes + + # TFTP (UDP/69) + + - name: Copy fake TFTP netcat script + copy: + dest: /usr/local/bin/fake-tftp-netcat-ens5.sh + mode: '0755' + content: | + #!/bin/bash + while true; do + ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') + if [[ -n "$ens5_IP" ]]; then break; fi + sleep 2 + done + while true; do + echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ens5_IP -p 69 + done + - name: Run fake TFTP netcat script as background process + shell: nohup /usr/local/bin/fake-tftp-netcat-ens5.sh > /var/log/fake-tftp-netcat-ens5.log 2>&1 & + args: + creates: /var/log/fake-tftp-netcat-ens5.log + # NTP - name: Create Python venv for scapy @@ -173,319 +325,162 @@ # FTP server - - name: Kill old fake FTP netcat processes - shell: pkill -f 'nc -l -s .* -p 21' || true - - ignore_errors: yes - - name: Copy fake FTP netcat script - copy: - dest: /usr/local/bin/fake-ftp-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "220 Fake FTP server ready\r\n" | nc -l -s $ens5_IP -p 21 - done - - name: Run fake FTP netcat script as background process - shell: nohup /usr/local/bin/fake-ftp-netcat-ens5.sh > /var/log/fake-ftp-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-ftp-netcat-ens5.log - - - # SSH server configuration - - - name: Gather facts - setup: + - name: Restart vsftpd + service: + name: vsftpd + state: restarted + enabled: yes - - name: Set ens4_ip from Ansible facts - set_fact: - ens4_ip: "{{ ansible_ens4.ipv4.address | default('') }}" + - name: Check if FTP is listening on port 21 + wait_for: + port: 21 + state: started + timeout: 20 - - name: Fail if ens4_ip is not set - fail: - msg: "ens4_ip is not set! Please check network configuration." - when: ens4_ip == '' - - name: Debug ens4 IP - debug: - msg: "ens4 IP is {{ ens4_ip }}" - - name: Stop and disable ssh.socket (prevents auto-start on all interfaces) - service: - name: ssh.socket - state: stopped - enabled: no - ignore_errors: yes + # Telnet server setup - - name: Ensure only ens4 is in ListenAddress for sshd + - name: Ensure telnetd is enabled in /etc/inetd.conf lineinfile: - path: /etc/ssh/sshd_config - regexp: '^ListenAddress' - line: "ListenAddress {{ ens4_ip }}" + path: /etc/inetd.conf + regexp: '^telnet' + line: 'telnet stream tcp nowait root /usr/sbin/telnetd telnetd' state: present - insertafter: '^#Port' - - - name: Remove all other ListenAddress lines from sshd_config - lineinfile: - path: /etc/ssh/sshd_config - regexp: '^ListenAddress (?!{{ ens4_ip }})' - state: absent - - name: Restart sshd + - name: Restart inetutils-inetd service: - name: ssh + name: inetutils-inetd state: restarted + enabled: yes - - name: Show what is listening on port 22 - shell: ss -tlnp | grep :22 || netstat -tlnp | grep :22 || true - register: port22 - changed_when: false + - name: Check if Telnet is listening on port 23 + wait_for: + port: 23 + state: started + timeout: 20 - - debug: - var: port22.stdout_lines + # SMTP (TCP/25) + - name: Ensure Postfix listens on all interfaces (0.0.0.0:25) + lineinfile: + path: /etc/postfix/main.cf + regexp: '^inet_interfaces =' + line: 'inet_interfaces = all' + state: present - - name: Create fake SSH banner systemd service for ens5:22 - copy: - dest: /etc/systemd/system/fake-ssh-banner.service - mode: '0644' - content: | - [Unit] - Description=Fake SSH Banner on ens5:22 - After=network-online.target - Wants=network-online.target + - name: Restart and enable Postfix service + service: + name: postfix + state: restarted + enabled: yes - [Service] - Type=simple - ExecStart=/bin/bash -c '\ - while true; do \ - ens5_IP=$(ip -4 addr show ens5 | grep -oP "(?<=inet\s)\d+(\.\d+){3}"); \ - if [[ -n "$ens5_IP" ]]; then \ - echo "ens5 IP detected: $ens5_IP"; \ - break; \ - fi; \ - echo "Waiting for ens5 IP..."; \ - sleep 2; \ - done; \ - while true; do echo -e "SSH-1.99-OpenSSH_1.99\r\n" | nc -l -s $ens5_IP -p 22; done' - Restart=always + - name: Wait for SMTP server to listen on port 25 + wait_for: + port: 25 + state: started + timeout: 20 - [Install] - WantedBy=multi-user.target + # POP3 (TCP/110) / IMAP (TCP/143) - - name: Reload systemd - systemd: - daemon_reload: yes - - name: Restart fake-ssh-banner service - systemd: - name: fake-ssh-banner.service + - name: Ensure Dovecot service is enabled and restarted + service: + name: dovecot state: restarted enabled: yes - # Telnet server setup - - - name: Kill old fake Telnet netcat processes - shell: pkill -f 'nc -l -s .* -p 23' || true - ignore_errors: yes - - name: Copy fake Telnet netcat script - copy: - dest: /usr/local/bin/fake-telnet-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "Fake Telnet server\r\n" | nc -l -s $ens5_IP -p 23 - done - - name: Run fake Telnet netcat script as background process - shell: nohup /usr/local/bin/fake-telnet-netcat-ens5.sh > /var/log/fake-telnet-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-telnet-netcat-ens5.log + - name: Wait for POP3 server to listen on port 110 + wait_for: + port: 110 + state: started + timeout: 20 - # SMTP (TCP/25) + - name: Wait for IMAP server to listen on port 143 + wait_for: + port: 143 + state: started + timeout: 20 - - name: Kill old fake SMTP netcat processes - shell: pkill -f 'nc -l -s .* -p 25' || true - ignore_errors: yes - - name: Copy fake SMTP netcat script - copy: - dest: /usr/local/bin/fake-smtp-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "220 fake-smtp ESMTP Postfix\r\n" | nc -l -s $ens5_IP -p 25 - done - - name: Run fake SMTP netcat script as background process - shell: nohup /usr/local/bin/fake-smtp-netcat-ens5.sh > /var/log/fake-smtp-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-smtp-netcat-ens5.log - # POP3 (TCP/110) + # HTTP (TCP/80) - - name: Kill old fake POP3 netcat processes + - name: Install nginx + apt: + name: nginx + state: present + - name: Ensure nginx is running + service: + name: nginx + state: started + enabled: yes + + # VNC (TCP/5901) - shell: pkill -f 'nc -l -s .* -p 110' || true - ignore_errors: yes - - name: Copy fake POP3 netcat script - copy: - dest: /usr/local/bin/fake-pop3-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "+OK Fake POP3 server ready\r\n" | nc -l -s $ens5_IP -p 110 - done - - name: Run fake POP3 netcat script as background process - shell: nohup /usr/local/bin/fake-pop3-netcat-ens5.sh > /var/log/fake-pop3-netcat-ens5.log 2>&1 & + - name: Set VNC password (optional, for security) + command: x11vnc -storepasswd "vncpassword" /etc/x11vnc.pass args: - creates: /var/log/fake-pop3-netcat-ens5.log + creates: /etc/x11vnc.pass - # IMAP (TCP/143) - - - name: Kill old fake IMAP netcat processes - shell: pkill -f 'nc -l -s .* -p 143' || true - ignore_errors: yes - - name: Copy fake IMAP netcat script + - name: Create systemd unit for Xvfb on display :0 copy: - dest: /usr/local/bin/fake-imap-netcat-ens5.sh - mode: '0755' + dest: /etc/systemd/system/xvfb.service + mode: '0644' content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "* OK Fake IMAP4rev1 Service Ready\r\n" | nc -l -s $ens5_IP -p 143 - done - - name: Run fake IMAP netcat script as background process - shell: nohup /usr/local/bin/fake-imap-netcat-ens5.sh > /var/log/fake-imap-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-imap-netcat-ens5.log + [Unit] + Description=Virtual X server (Xvfb) on :0 + After=network.target - # HTTP (TCP/80) + [Service] + Type=simple + ExecStart=/usr/bin/Xvfb :0 -screen 0 1024x768x16 + Restart=always - - name: Kill old fake HTTP netcat processes - shell: pkill -f 'nc -l -s .* -p 80' || true - ignore_errors: yes - - name: Copy fake HTTP netcat script + [Install] + WantedBy=multi-user.target + + - name: Create systemd unit for x11vnc on port 5901 copy: - dest: /usr/local/bin/fake-http-netcat-ens5.sh - mode: '0755' + dest: /etc/systemd/system/x11vnc.service + mode: '0644' content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "HTTP/1.1 200 OK\r\nContent-Length: 2\r\n\r\nOK" | nc -l -s $ens5_IP -p 80 - done - - name: Run fake HTTP netcat script as background process - shell: nohup /usr/local/bin/fake-http-netcat-ens5.sh > /var/log/fake-http-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-http-netcat-ens5.log + [Unit] + Description=Start x11vnc at startup. + After=xvfb.service - # SNMP (UDP/161) + [Service] + Type=simple + ExecStart=/usr/bin/x11vnc -display :0 -rfbauth /etc/x11vnc.pass -forever -shared -rfbport 5901 -nopw -listen 0.0.0.0 + Restart=always - - name: Kill old fake SNMP netcat processes - shell: pkill -f 'nc -u -l -s .* -p 161' || true - ignore_errors: yes - - name: Copy fake SNMP netcat script - copy: - dest: /usr/local/bin/fake-snmp-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - head -c 48 /dev/zero | nc -u -l -s $ens5_IP -p 161 - done - - name: Run fake SNMP netcat script as background process - shell: nohup /usr/local/bin/fake-snmp-netcat-ens5.sh > /var/log/fake-snmp-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-snmp-netcat-ens5.log + [Install] + WantedBy=multi-user.target - # VNC (TCP/5901) + - name: Reload systemd daemon + systemd: + daemon_reload: yes - - name: Kill old fake VNC netcat processes - shell: pkill -f 'nc -l -s .* -p 5901' || true - ignore_errors: yes - - name: Copy fake VNC netcat script - copy: - dest: /usr/local/bin/fake-vnc-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "RFB 003.008\n" | nc -l -s $ens5_IP -p 5901 - done - - name: Run fake VNC netcat script as background process - shell: nohup /usr/local/bin/fake-vnc-netcat-ens5.sh > /var/log/fake-vnc-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-vnc-netcat-ens5.log + - name: Enable and start Xvfb service + systemd: + name: xvfb + enabled: yes + state: started - # TFTP (UDP/69) + - name: Enable and start x11vnc service + systemd: + name: x11vnc + enabled: yes + state: started + + - name: Wait for VNC server to listen on port 5901 + wait_for: + port: 5901 + state: started + timeout: 60 - - name: Kill old fake TFTP netcat processes - shell: pkill -f 'nc -u -l -s .* -p 69' || true - ignore_errors: yes - - name: Copy fake TFTP netcat script - copy: - dest: /usr/local/bin/fake-tftp-netcat-ens5.sh - mode: '0755' - content: | - #!/bin/bash - while true; do - ens5_IP=$(ip -4 addr show ens5 | grep -oP '(?<=inet\s)\d+(\.\d+){3}') - if [[ -n "$ens5_IP" ]]; then break; fi - sleep 2 - done - while true; do - echo -ne "\x00\x05\x00\x01File not found\x00" | nc -u -l -s $ens5_IP -p 69 - done - - name: Run fake TFTP netcat script as background process - shell: nohup /usr/local/bin/fake-tftp-netcat-ens5.sh > /var/log/fake-tftp-netcat-ens5.log 2>&1 & - args: - creates: /var/log/fake-tftp-netcat-ens5.log # NTP (UDP/123) - - name: Kill old fake NTP netcat processes - shell: pkill -f 'nc -u -l -s .* -p 123' || true - ignore_errors: yes - name: Copy fake NTP netcat script copy: dest: /usr/local/bin/fake-ntp-netcat-ens5.sh @@ -526,9 +521,6 @@ echo -ne "Fake BACnet server\n" | nc -u -l -s $ens5_IP -p 47808 done - - - # DHCP - name: Install ISC DHCP server @@ -575,6 +567,72 @@ msg: "DHCP server status: {{ dhcp_status.stdout }}" + - name: Create Python venv for scapy + command: python3 -m venv {{ scapy_venv_path }} + args: + creates: "{{ scapy_venv_path }}/bin/activate" + changed_when: false + + - name: Install scapy in venv + command: "{{ scapy_venv_path }}/bin/pip install scapy" + changed_when: false + + - name: Install netifaces in scapy venv + command: "{{ scapy_venv_path }}/bin/pip install netifaces" + changed_when: false + + - name: Copy ARP spoof script (continuous, Scapy, wrong IP) + copy: + dest: /usr/local/bin/send_wrong_arp.py + mode: '0755' + content: | + from scapy.all import ARP, Ether, sendp + import time + + iface = "{{ iface }}" + src_mac = "{{ device_mac }}" + wrong_ip = "{{ wrong_ip }}" + target_ip = "{{ target_ip }}" + + while True: + arp = Ether(src=src_mac, dst="ff:ff:ff:ff:ff:ff")/ARP( + op=2, + hwsrc=src_mac, + psrc=wrong_ip, + hwdst="ff:ff:ff:ff:ff:ff", + pdst=target_ip + ) + sendp(arp, iface=iface, count=1, verbose=False) + print(f"Sent ARP reply: {wrong_ip} is-at {src_mac} to {target_ip}") + time.sleep(1) + + - name: Create systemd unit for continuous ARP spoof + copy: + dest: /etc/systemd/system/send-wrong-arp.service + mode: '0644' + content: | + [Unit] + Description=Continuous ARP Spoof Sender + After=network.target + + [Service] + Type=simple + ExecStart={{ scapy_venv_path }}/bin/python /usr/local/bin/send_wrong_arp.py + Restart=always + User=root + + [Install] + WantedBy=multi-user.target + + - name: Reload systemd daemon + systemd: + daemon_reload: yes + + - name: Enable and start continuous ARP spoof service + systemd: + name: send-wrong-arp + enabled: yes + state: started # BLOCK ICMP @@ -624,11 +682,6 @@ sysctl_set: yes reload: yes - - - name: Remove temporary static IP from ens5 - command: ip addr del 10.10.10.14/24 dev ens5 - ignore_errors: yes - handlers: - name: Restart vsftpd service: diff --git a/testing/api/devices/device_1/device_config.json b/testing/api/devices/device_1/device_config.json index 3be69a082..9e3f84328 100644 --- a/testing/api/devices/device_1/device_config.json +++ b/testing/api/devices/device_1/device_config.json @@ -50,5 +50,121 @@ "dns": { "enabled": true } - } + }, + "reports": [{ + "testrun": { + "version": "2.1" + }, + "mac_addr": null, + "device": { + "mac_addr": "00:1e:42:35:73:c4", + "manufacturer": "Teltonika", + "model": "TRB140", + "firmware": "1", + "test_modules": { + "protocol": { + "enabled": false + }, + "services": { + "enabled": false + }, + "connection": { + "enabled": false + }, + "tls": { + "enabled": true + }, + "ntp": { + "enabled": false + }, + "dns": { + "enabled": false + } + }, + "test_pack": "Device Qualification", + "device_profile": [ + { + "question": "What type of device is this?", + "answer": "Building Automation Gateway" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Access Control" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "Yes" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "No" + } + ] + }, + "status": "Non-Compliant", + "started": "2024-12-10 16:06:42", + "finished": "2024-12-10 16:08:12", + "tests": { + "total": 5, + "results": [ + { + "name": "security.tls.v1_0_client", + "description": "No outbound connections were found", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support", + "required_result": "Informational", + "result": "Feature Not Detected" + }, + { + "name": "security.tls.v1_2_server", + "description": "TLS 1.2 certificate is invalid", + "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed", + "required_result": "Required if Applicable", + "result": "Non-Compliant", + "recommendations": [ + "Enable TLS 1.2 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_2_client", + "description": "An error occurred whilst running this test", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers", + "required_result": "Required if Applicable", + "result": "Error" + }, + { + "name": "security.tls.v1_3_server", + "description": "TLS 1.3 certificate is invalid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "required_result": "Informational", + "result": "Informational", + "optional_recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "description": "An error occurred whilst running this test", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", + "required_result": "Informational", + "result": "Error" + } + ] + }, + "report": "/report/001e42289e4a_2024-12-10T16:06:42", + "export": "/export/001e42289e4a_2024-12-10T16:06:42", + "folder_name": "001e42289e4a_2024-12-10T16:06:42" +} +] } diff --git a/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json new file mode 100644 index 000000000..7dc70a7b5 --- /dev/null +++ b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.json @@ -0,0 +1,116 @@ +{ + "testrun": { + "version": "2.1" + }, + "mac_addr": null, + "device": { + "mac_addr": "00:1e:42:35:73:c4", + "manufacturer": "Teltonika", + "model": "TRB140", + "firmware": "1", + "test_modules": { + "protocol": { + "enabled": false + }, + "services": { + "enabled": false + }, + "connection": { + "enabled": false + }, + "tls": { + "enabled": true + }, + "ntp": { + "enabled": false + }, + "dns": { + "enabled": false + } + }, + "test_pack": "Device Qualification", + "device_profile": [ + { + "question": "What type of device is this?", + "answer": "Building Automation Gateway" + }, + { + "question": "Please select the technology this device falls into", + "answer": "Hardware - Access Control" + }, + { + "question": "Does your device process any sensitive information? ", + "answer": "No" + }, + { + "question": "Can all non-essential services be disabled on your device?", + "answer": "Yes" + }, + { + "question": "Is there a second IP port on the device?", + "answer": "Yes" + }, + { + "question": "Can the second IP port on your device be disabled?", + "answer": "No" + } + ] + }, + "status": "Non-Compliant", + "started": "2024-12-10 16:06:42", + "finished": "2024-12-10 16:08:12", + "tests": { + "total": 5, + "results": [ + { + "name": "security.tls.v1_0_client", + "description": "No outbound connections were found", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.0 and support", + "required_result": "Informational", + "result": "Feature Not Detected" + }, + { + "name": "security.tls.v1_2_server", + "description": "TLS 1.2 certificate is invalid", + "expected_behavior": "TLS 1.2 certificate is issued to the web browser client when accessed", + "required_result": "Required if Applicable", + "result": "Non-Compliant", + "recommendations": [ + "Enable TLS 1.2 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_2_client", + "description": "An error occurred whilst running this test", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.2 and support for ECDH and ECDSA ciphers", + "required_result": "Required if Applicable", + "result": "Error" + }, + { + "name": "security.tls.v1_3_server", + "description": "TLS 1.3 certificate is invalid", + "expected_behavior": "TLS 1.3 certificate is issued to the web browser client when accessed", + "required_result": "Informational", + "result": "Informational", + "optional_recommendations": [ + "Enable TLS 1.3 support in the web server configuration", + "Disable TLS 1.0 and 1.1", + "Sign the certificate used by the web server" + ] + }, + { + "name": "security.tls.v1_3_client", + "description": "An error occurred whilst running this test", + "expected_behavior": "The packet indicates a TLS connection with at least TLS 1.3", + "required_result": "Informational", + "result": "Error" + } + ] + }, + "report": "/report/001e42289e4a_2024-12-10T16:06:42", + "export": "/export/001e42289e4a_2024-12-10T16:06:42", + "folder_name": "001e42289e4a_2024-12-10T16:06:42" + +} \ No newline at end of file diff --git a/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf new file mode 100644 index 000000000..87fc93be2 Binary files /dev/null and b/testing/api/local/reports/001e42289e4a_2024-12-10T16:06:42/test/001e42289e4a/report.pdf differ diff --git a/testing/api/reports/report.json b/testing/api/reports/report.json index e1d9f7003..7dc70a7b5 100644 --- a/testing/api/reports/report.json +++ b/testing/api/reports/report.json @@ -109,5 +109,8 @@ } ] }, - "report": "http://localhost:8000/report/Teltonika TRB140/2024-12-10T16:06:42" + "report": "/report/001e42289e4a_2024-12-10T16:06:42", + "export": "/export/001e42289e4a_2024-12-10T16:06:42", + "folder_name": "001e42289e4a_2024-12-10T16:06:42" + } \ No newline at end of file diff --git a/testing/api/test_api b/testing/api/test_api index 095123a3b..649720205 100755 --- a/testing/api/test_api +++ b/testing/api/test_api @@ -38,6 +38,7 @@ sudo chown -R $USER local # Copy configuration to testrun sudo cp testing/api/sys_config/system.json local/system.json +sudo cp testing/api/local/reports local/ -r # Needs to be sudo because this invokes bin/testrun sudo venv/bin/python3 -m pytest -v testing/api/test_api.py diff --git a/testing/api/test_api.py b/testing/api/test_api.py index 9ba6c3f08..431aac56f 100644 --- a/testing/api/test_api.py +++ b/testing/api/test_api.py @@ -934,7 +934,6 @@ def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613 "status", "started", "finished", - "tests", "report", "export" ] @@ -948,9 +947,9 @@ def test_get_reports(empty_devices_dir, add_devices, # pylint: disable=W0613 @pytest.mark.parametrize("add_devices", [ ["device_1"] ],indirect=True) -def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """ Test for succesfully delete a report (200) """ +def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """Test for successfully get report when report exists (200)""" # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -958,86 +957,85 @@ def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613 # Assign the device mac address mac_addr = device["mac_addr"] - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Payload - delete_data = { - "mac_addr": mac_addr, - "timestamp": get_timestamp() - } + # Construct report_name + mac_no_colons = mac_addr.replace(":", "") + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" - # Send a DELETE request to remove the report - r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5) + # Send the get request + r = requests.get(f"{API}/report/{report_name}", timeout=5) - # Check if status code is 200 (OK) + # Check if status code is 200 (ok) assert r.status_code == 200 - # Parse the json response - response = r.json() - - # Check if "success" in response - assert "success" in response + # Check if the response is a PDF + assert r.headers["Content-Type"] == "application/pdf" - # Construct the 'reports' folder path - reports_folder = os.path.join(device_name, "reports") +@pytest.mark.parametrize("add_devices, add_profiles", [ + (["device_1"], ["valid_profile.json"]) +], indirect=True) +def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + empty_profiles_dir, add_profiles, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """Test export results with existing profile when report exists (200)""" - # Check if reports folder has been deleted - assert not os.path.exists(reports_folder) + # Load the profile using load_json utility method + profile = load_json("valid_profile.json", directory=PROFILES_PATH) -@pytest.mark.parametrize("add_devices", [ - ["device_1"] -],indirect=True) -def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """ Test delete report bad request when the payload is missing (400) """ + # Load the device using load_json utility method + device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Send a DELETE request to remove the report without the payload - r = requests.delete(f"{API}/report", timeout=5) + # Assign the device mac address + mac_addr = device["mac_addr"] - # Check if status code is 400 (bad request) - assert r.status_code == 400 + # Construct report_name + mac_no_colons = mac_addr.replace(":", "") + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" - # Parse the json response - response = r.json() + # Send the post request + r = requests.post(f"{API}/export/{report_name}", + json=profile, + timeout=5) - # Check if "error" in response - assert "error" in response + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Check if the correct error message returned - assert "Invalid request received, missing body" in response["error"] + # Check if the response is a zip file + assert r.headers["Content-Type"] == "application/zip" @pytest.mark.parametrize("add_devices", [ ["device_1"] ],indirect=True) -def test_delete_report_invalid_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """ Test delete report bad request missing mac addr or timestamp (400) """ - - # Empty payload - delete_data = {} +def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """Test export results with no profile when report exists (200)""" + # Load the device using load_json utility method + device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Send a DELETE request to remove the report - r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5) + # Assign the device mac address + mac_addr = device["mac_addr"] - # Check if status code is 400 (bad request) - assert r.status_code == 400 + # Construct report_name + mac_no_colons = mac_addr.replace(":", "") + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" - # Parse the json response - response = r.json() + # Send the post request + r = requests.post(f"{API}/export/{report_name}", timeout=5) - # Check if "error" in response - assert "error" in response + # Check if status code is 200 (OK) + assert r.status_code == 200 - # Check if the correct error message returned - assert "Missing mac address or timestamp" in response["error"] + # Check if the response is a zip file + assert r.headers["Content-Type"] == "application/zip" @pytest.mark.parametrize("add_devices", [ ["device_1"] ],indirect=True) -def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """ Test delete report bad request if timestamp format is not valid (400) """ +def test_delete_report(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test for succesfully delete a report (200) """ # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) @@ -1045,41 +1043,56 @@ def test_delete_report_invalid_timestamp(empty_devices_dir, add_devices, # pylin # Assign the device mac address mac_addr = device["mac_addr"] - # Assign the incorrect timestamp format - invalid_timestamp = "2024-01-01 invalid" + # Assign the device name + device_name = f'{device["manufacturer"]} {device["model"]}' - # Payload - delete_data = { - "mac_addr": mac_addr, - "timestamp": invalid_timestamp - } + # Construct report_name from mac_addr and timestamp + mac_no_colons = mac_addr.replace(":", "") + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" # Send a DELETE request to remove the report - r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5) + r = requests.delete(f"{API}/report/{report_name}", timeout=5) - # Check if status code is 400 (bad request) - assert r.status_code == 400 + # Check if status code is 200 (OK) + assert r.status_code == 200 # Parse the json response response = r.json() - # Check if "error" in response - assert "error" in response + # Check if "success" in response + assert "success" in response + + # Construct the 'reports' folder path + reports_folder = os.path.join(device_name, "reports") + + # Check if reports folder has been deleted + assert not os.path.exists(reports_folder) + +@pytest.mark.parametrize("add_devices", [ + ["device_1"] +],indirect=True) +def test_delete_report_no_payload(empty_devices_dir, add_devices, # pylint: disable=W0613 + create_report_folder, testrun): # pylint: disable=W0613 + """ Test delete report with empty report name (404) """ + + # Send a DELETE request with empty report name + r = requests.delete(f"{API}/report/", timeout=5) + + # Check if status code is 404 (not found) + assert r.status_code == 404 - # Check if the correct error message returned - assert "Incorrect timestamp format" in response["error"] def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable=W0613 """ Test delete report when device does not exist (404) """ - # Payload to be deleted for a non existing device - delete_data = { - "mac_addr": "00:1e:42:35:73:c4", - "timestamp": get_timestamp() - } + # Construct report_name for non-existing device + mac_no_colons = "001e423573c4" + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" # Send the delete request to the endpoint - r = requests.delete(f"{API}/report", data=json.dumps(delete_data), timeout=5) + r = requests.delete(f"{API}/report/{report_name}", timeout=5) # Check if status is 404 (not found) assert r.status_code == 404 @@ -1091,7 +1104,7 @@ def test_delete_report_no_device(empty_devices_dir, testrun): # pylint: disable= assert "error" in response # Check if the correct error message returned - assert "Could not find device" in response["error"] + assert "Report not found" in response["error"] @pytest.mark.parametrize("add_devices", [ ["device_1"] @@ -1105,16 +1118,13 @@ def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pyl # Assign the device mac address mac_addr = device["mac_addr"] - # Prepare the payload for the DELETE request - delete_data = { - "mac_addr": mac_addr, - "timestamp": get_timestamp() - } + # Construct report_name for non-existent report + mac_no_colons = mac_addr.replace(":", "") + # Use different timestamp than what exists + invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00" # Send the delete request to delete the report - r = requests.delete(f"{API}/report", - data=json.dumps(delete_data), - timeout=5) + r = requests.delete(f"{API}/report/{invalid_report_name}", timeout=5) # Check if status code is 404 (not found) assert r.status_code == 404 @@ -1128,31 +1138,6 @@ def test_delete_report_no_report(empty_devices_dir, add_devices, testrun): # pyl # Check if the correct error message is returned assert "Report not found" in response["error"] -@pytest.mark.parametrize("add_devices", [ - ["device_1"] -],indirect=True) -def test_get_report_success(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test for successfully get report when report exists (200)""" - - # Load the device using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) - - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Assign the timestamp and change the format - timestamp = get_timestamp(formatted=True) - - # Send the get request - r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) - - # Check if status code is 200 (ok) - assert r.status_code == 200 - - # Check if the response is a PDF - assert r.headers["Content-Type"] == "application/pdf" - @pytest.mark.parametrize("add_devices", [ ["device_1"] ],indirect=True) @@ -1162,14 +1147,15 @@ def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the device mac address + mac_addr = device["mac_addr"] - # Assign the timestamp - timestamp = get_timestamp() + # Construct report_name with non-existent timestamp + mac_no_colons = mac_addr.replace(":", "") + invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00" # Send the get request - r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) + r = requests.get(f"{API}/report/{invalid_report_name}", timeout=5) # Check if status code is 404 (not found) assert r.status_code == 404 @@ -1180,20 +1166,23 @@ def test_get_report_not_found(empty_devices_dir, add_devices, testrun): # pylint # Check if "error" in response assert "error" in response + # Check if the correct error message is returned + assert "Report not found from list" in response["error"] + + # Check if "error" in response + assert "error" in response + # Check if the correct error message returned - assert "Report could not be found" in response["error"] + assert "Report not found from list" in response["error"] def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disable=W0613 """Test getting a report when the device is not found (404)""" - # Assign device name - device_name = "nonexistent_device" - # Assign the timestamp timestamp = get_timestamp() # Send the get request - r = requests.get(f"{API}/report/{device_name}/{timestamp}", timeout=5) + r = requests.get(f"{API}/report/001e423573c4_{timestamp}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1205,20 +1194,20 @@ def test_get_report_device_not_found(empty_devices_dir, testrun): # pylint: disa assert "error" in response # Check if the correct error message is returned - assert "Device not found" in response["error"] + assert "Report not found from list" in response["error"] def test_export_report_device_not_found(empty_devices_dir, create_report_folder, # pylint: disable=W0613 testrun): # pylint: disable=W0613 """Test for export the report result when the device could not be found""" - # Assign the non-existing device name - device_name = "non existing device" - + # Assign the non-existing device mac (no colons) + mac_no_colons = "001e423573c4" # Assign the timestamp - timestamp = get_timestamp() + timestamp = get_timestamp(formatted=True) + invalid_report_name = f"{mac_no_colons}_{timestamp}" # Send the post request - r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) + r = requests.post(f"{API}/export/{invalid_report_name}", timeout=5) # Check if is 404 (not found) assert r.status_code == 404 @@ -1229,8 +1218,8 @@ def test_export_report_device_not_found(empty_devices_dir, create_report_folder, # Check if "error" in response assert "error" in response - # Check if the correct error message returned - assert "A device with that name could not be found" in response["error"] + # Check if the correct error message is returned + assert "Report not found from list" in response["error"] @pytest.mark.parametrize("add_devices", [ ["device_1"] @@ -1242,17 +1231,19 @@ def test_export_report_profile_not_found(empty_devices_dir, add_devices, # pylin # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the device mac address + mac_addr = device["mac_addr"] - # Assign the timestamp - timestamp = get_timestamp() + # Construct report_name + mac_no_colons = mac_addr.replace(":", "") + timestamp = get_timestamp(formatted=True) + report_name = f"{mac_no_colons}_{timestamp}" # Add a non existing profile into the payload payload = {"profile": "non_existent_profile"} # Send the post request - r = requests.post(f"{API}/export/{device_name}/{timestamp}", + r = requests.post(f"{API}/export/{report_name}", json=payload, timeout=5) @@ -1277,14 +1268,15 @@ def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pyl # Load the device using load_json utility method device = load_json("device_config.json", directory=DEVICE_1_PATH) - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' + # Assign the device mac address + mac_addr = device["mac_addr"] - # Assign the timestamp - timestamp = get_timestamp() + # Construct report_name + mac_no_colons = mac_addr.replace(":", "") + invalid_report_name = f"{mac_no_colons}_2020-01-01T00:00:00" # Send the post request to trigger the zipping process - r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=10) + r = requests.post(f"{API}/export/{invalid_report_name}", timeout=10) # Check if status code is 404 (Not Found) assert r.status_code == 404 @@ -1296,63 +1288,7 @@ def test_export_report_not_found(empty_devices_dir, add_devices, testrun): # pyl assert "error" in response # Check if the correct error message is returned - assert "Report could not be found" in response["error"] - -@pytest.mark.parametrize("add_devices, add_profiles", [ - (["device_1"], ["valid_profile.json"]) -], indirect=True) -def test_export_report_with_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 - empty_profiles_dir, add_profiles, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test export results with existing profile when report exists (200)""" - - # Load the profile using load_json utility method - profile = load_json("valid_profile.json", directory=PROFILES_PATH) - - # Load the device using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) - - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Assign the timestamp and change the format - timestamp = get_timestamp(formatted=True) - - # Send the post request - r = requests.post(f"{API}/export/{device_name}/{timestamp}", - json=profile, - timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Check if the response is a zip file - assert r.headers["Content-Type"] == "application/zip" - -@pytest.mark.parametrize("add_devices", [ - ["device_1"] -],indirect=True) -def test_export_results_with_no_profile(empty_devices_dir, add_devices, # pylint: disable=W0613 - create_report_folder, testrun): # pylint: disable=W0613 - """Test export results with no profile when report exists (200)""" - - # Load the device using load_json utility method - device = load_json("device_config.json", directory=DEVICE_1_PATH) - - # Assign the device name - device_name = f'{device["manufacturer"]} {device["model"]}' - - # Assign the timestamp and change the format - timestamp = get_timestamp(formatted=True) - - # Send the post request - r = requests.post(f"{API}/export/{device_name}/{timestamp}", timeout=5) - - # Check if status code is 200 (OK) - assert r.status_code == 200 - - # Check if the response is a zip file - assert r.headers["Content-Type"] == "application/zip" + assert "Report not found from list" in response["error"] # Tests for device endpoints @pytest.fixture() diff --git a/testing/unit/protocol/protocol_module_test.py b/testing/unit/protocol/protocol_module_test.py index 9d474ab91..063fd9401 100644 --- a/testing/unit/protocol/protocol_module_test.py +++ b/testing/unit/protocol/protocol_module_test.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. """Module run all the DNS related unit tests""" -from protocol_bacnet import BACnet +from protocol_bacnet import BACnet, BACnetDevice import unittest import os import sys @@ -52,8 +52,10 @@ def setUpClass(cls): # Test the BACNet traffic for a matching Object ID and HW address def bacnet_protocol_traffic_test(self): LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}') - result = BACNET.validate_bacnet_source(object_id='1761001', - device_hw_addr=HW_ADDR) + result = BACNET.validate_bacnet_source( + BACnetDevice(device_id='1761001', ip='10.10.10.14'), + device_hw_addr=HW_ADDR + ) LOGGER.info(f'Test Result: {result}') self.assertEqual(result, True) @@ -61,8 +63,10 @@ def bacnet_protocol_traffic_test(self): # do not match def bacnet_protocol_traffic_fail_test(self): LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}') - result = BACNET.validate_bacnet_source(object_id='1761001', - device_hw_addr=HW_ADDR_BAD) + result = BACNET.validate_bacnet_source( + BACnetDevice(device_id='1761001', ip='10.10.10.14'), + device_hw_addr=HW_ADDR_BAD + ) LOGGER.info(f'Test Result: {result}') self.assertEqual(result, False) @@ -71,7 +75,7 @@ def bacnet_protocol_traffic_fail_test(self): def bacnet_protocol_validate_device_test(self): LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}') # Load bacnet devices to simulate a discovery - bac_dev = ('TestDevice', 'Testrun', '10.10.10.14', 1761001) + bac_dev = BACnetDevice(device_id='1761001', ip='10.10.10.14') BACNET.devices = [bac_dev] result = BACNET.validate_device() LOGGER.info(f'Test Result: {result}') @@ -82,7 +86,7 @@ def bacnet_protocol_validate_device_test(self): def bacnet_protocol_validate_device_fail_test(self): LOGGER.info(f'Running { inspect.currentframe().f_code.co_name}') # Load bacnet devices to simulate a discovery - bac_dev = ('TestDevice', 'Testrun', '10.10.10.14', 1761001) + bac_dev = BACnetDevice(device_id='1761001', ip='10.10.10.14') BACNET.devices = [bac_dev] # Change the MAC address to a different device than expected BACNET.device_hw_addr = HW_ADDR_BAD diff --git a/testing/unit/tls/tls_module_test.py b/testing/unit/tls/tls_module_test.py index f1e47f55c..a0364b4ce 100644 --- a/testing/unit/tls/tls_module_test.py +++ b/testing/unit/tls/tls_module_test.py @@ -80,7 +80,7 @@ def security_tls_v1_2_server_no_ip_test(self): self.assertEqual(result, 'Error') self.assertEqual(description, 'Could not resolve device IP address') - self.assertEqual(details, ['Could not resolve device IP address.']) + self.assertEqual(details, []) def security_tls_v1_2_server_no_scan_results_test(self): """Tests _security_tls_v1_2_server when scan finds no HTTP/HTTPS ports""" @@ -607,9 +607,9 @@ def security_tls_client_allowed_protocols_test(self): # Run the client test test_results = TLS_UTIL.validate_tls_client(client_mac='e4:5f:01:5f:92:9c', - tls_version='1.2', + tls_version='1.3', capture_files=[capture_file]) - print(str(test_results)) + print('results', str(test_results)) self.assertTrue(test_results[0]) def outbound_connections_test(self):