diff --git a/.travis.yml b/.travis.yml index 2452b5b..d98caaa 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,11 +5,20 @@ python: - 2.7 env: - TEST_PATH=tests/data + +services: + - docker # - 3.6 #matrix: # allow_failures: # - python: 3.6 - Commenting out testing for Python 3.6 until ready +before_install: + - mkdir -p ./data/esdata1 + - mkdir -p ./data/es_snapshots + - sudo chown -R 1000:1000 ./data/es* + - docker build -t vulnwhisperer-local . + - docker-compose -f docker-compose-test.yml up -d install: - pip install -r requirements.txt - pip install flake8 # pytest # add another testing frameworks later @@ -20,25 +29,8 @@ before_script: - flake8 . --count --exit-zero --exclude=deps/qualysapi --max-complexity=10 --max-line-length=127 --statistics script: - python setup.py install - # Test successful scan download and parsing - - rm -rf /tmp/VulnWhisperer - - vuln_whisperer -c configs/test.ini --mock --mock_dir ${TEST_PATH} - # Run a second time with no scans to import - - vuln_whisperer -c configs/test.ini --mock --mock_dir ${TEST_PATH} - # Test one failed scan - - rm -rf /tmp/VulnWhisperer - - rm -f ${TEST_PATH}/nessus/GET_scans_exports_164_download - - vuln_whisperer -c configs/test.ini --mock --mock_dir ${TEST_PATH}; [[ $? -eq 1 ]] - # Test two failed scans - - rm -rf /tmp/VulnWhisperer - - rm -f ${TEST_PATH}/qualys_vuln/scan_1553941061.87241 - - vuln_whisperer -c configs/test.ini --mock --mock_dir ${TEST_PATH}; [[ $? -eq 2 ]] - # Test only nessus - - rm -rf /tmp/VulnWhisperer - - vuln_whisperer -c configs/test.ini -s nessus --mock --mock_dir ${TEST_PATH}; [[ $? -eq 1 ]] - # Test only qualy_vuln - - rm -rf /tmp/VulnWhisperer - - vuln_whisperer -c configs/test.ini -s qualys_vuln --mock --mock_dir ${TEST_PATH}; [[ $? -eq 1 ]] + - bash tests/test-vuln_whisperer.sh + - bash tests/test-docker.sh notifications: on_success: change on_failure: change # `always` will be the setting once code changes slow down diff --git a/Dockerfile b/Dockerfile index a2806ee..667cba1 100644 --- a/Dockerfile +++ b/Dockerfile @@ -20,8 +20,7 @@ RUN python setup.py clean --all && \ WORKDIR /opt/VulnWhisperer -RUN python setup.py install && \ - ln -s /opt/VulnWhisperer /tmp/VulnWhisperer +RUN python setup.py install CMD vuln_whisperer -c /opt/VulnWhisperer/frameworks_example.ini diff --git a/configs/test.ini b/configs/test.ini index b8ce72f..b5f04b5 100755 --- a/configs/test.ini +++ b/configs/test.ini @@ -4,8 +4,8 @@ hostname=nessus port=443 username=nessus_username password=nessus_password -write_path=/tmp/VulnWhisperer/data/nessus/ -db_path=/tmp/VulnWhisperer/data/database +write_path=/opt/VulnWhisperer/data/nessus/ +db_path=/opt/VulnWhisperer/data/database trash=false verbose=true @@ -15,8 +15,8 @@ hostname=tenable port=443 username=tenable.io_username password=tenable.io_password -write_path=/tmp/VulnWhisperer/data/tenable/ -db_path=/tmp/VulnWhisperer/data/database +write_path=/opt/VulnWhisperer/data/tenable/ +db_path=/opt/VulnWhisperer/data/database trash=false verbose=true @@ -26,8 +26,8 @@ enabled = false hostname = qualys_web username = exampleuser password = examplepass -write_path=/tmp/VulnWhisperer/data/qualys_web/ -db_path=/tmp/VulnWhisperer/data/database +write_path=/opt/VulnWhisperer/data/qualys_web/ +db_path=/opt/VulnWhisperer/data/database verbose=true # Set the maximum number of retries each connection should attempt. @@ -42,8 +42,8 @@ enabled = true hostname = qualys_vuln username = exampleuser password = examplepass -write_path=/tmp/VulnWhisperer/data/qualys_vuln/ -db_path=/tmp/VulnWhisperer/data/database +write_path=/opt/VulnWhisperer/data/qualys_vuln/ +db_path=/opt/VulnWhisperer/data/database verbose=true [detectify] @@ -54,8 +54,8 @@ hostname = detectify username = exampleuser #password variable used as secretKey password = examplepass -write_path =/tmp/VulnWhisperer/data/detectify/ -db_path = /tmp/VulnWhisperer/data/database +write_path =/opt/VulnWhisperer/data/detectify/ +db_path = /opt/VulnWhisperer/data/database verbose = true [openvas] @@ -64,8 +64,8 @@ hostname = openvas port = 4000 username = exampleuser password = examplepass -write_path=/tmp/VulnWhisperer/data/openvas/ -db_path=/tmp/VulnWhisperer/data/database +write_path=/opt/VulnWhisperer/data/openvas/ +db_path=/opt/VulnWhisperer/data/database verbose=true [jira] @@ -73,8 +73,8 @@ enabled = false hostname = jira-host username = username password = password -write_path = /tmp/VulnWhisperer/data/jira/ -db_path = /tmp/VulnWhisperer/data/database +write_path = /opt/VulnWhisperer/data/jira/ +db_path = /opt/VulnWhisperer/data/database verbose = true dns_resolv = False diff --git a/docker-compose-test.yml b/docker-compose-test.yml index 9beb9fc..a29ecc5 100644 --- a/docker-compose-test.yml +++ b/docker-compose-test.yml @@ -67,6 +67,8 @@ services: - xpack.monitoring.enabled=false depends_on: - elasticsearch + ports: + - 9600:9600 networks: esnet: aliases: diff --git a/resources/elk6/init_kibana.sh b/resources/elk6/init_kibana.sh index eca079d..656160c 100755 --- a/resources/elk6/init_kibana.sh +++ b/resources/elk6/init_kibana.sh @@ -12,7 +12,7 @@ saved_objects_file="kibana_APIonly.json" until curl -s "$elasticsearch_url/_cluster/health?pretty" | grep '"status"' | grep -qE "green|yellow"; do curl -s "$elasticsearch_url/_cluster/health?pretty" - echo "Waiting for Elasticsearch" + echo "Waiting for Elasticsearch..." sleep 5 done @@ -30,8 +30,8 @@ else fi until [ "`curl -s -I "$kibana_url"/status | head -n1 |cut -d$' ' -f2`" == "200" ]; do - curl -I "$kibana_url"/status - echo "Waiting for Kibana" + curl -s -I "$kibana_url"/status + echo "Waiting for Kibana..." sleep 5 done diff --git a/tests/test-docker.sh b/tests/test-docker.sh new file mode 100755 index 0000000..3d15b76 --- /dev/null +++ b/tests/test-docker.sh @@ -0,0 +1,109 @@ +#!/usr/bin/env bash + +NORMAL=$(tput sgr0) +GREEN=$(tput setaf 2) +YELLOW=$(tput setaf 3) +RED=$(tput setaf 1) + +function red() { + echo -e "$RED$*$NORMAL" +} + +function green() { + echo -e "$GREEN$*$NORMAL" +} + +function yellow() { + echo -e "$YELLOW$*$NORMAL" +} + +return_code=0 + +elasticsearch_url="localhost:9200" +logstash_url="localhost:9600" + +until curl -s "$elasticsearch_url/_cluster/health?pretty" | grep '"status"' | grep -qE "green|yellow"; do + yellow "Waiting for Elasticsearch..." + sleep 5 +done +green "✅ Elasticsearch status is green..." + +count=0 +until [[ $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1236 ]]; do + yellow "Waiting for Logstash load to finish... $(curl -s "$logstash_url/_node/stats" | jq '.events.out') of 1236 (attempt $count of 60)" + ((count++)) && ((count==60)) && break + sleep 5 +done + +if [[ count -le 60 && $(curl -s "$logstash_url/_node/stats" | jq '.events.out') -ge 1236 ]]; then + green "✅ Logstash load finished..." +else + red "❌ Logstash load didn't complete... $(curl -s "$logstash_url/_node/stats" | jq '.events.out')" +fi + + +count=0 +until [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') -ge 1232 ]] ; do + yellow "Waiting for Elasticsearch index to sync... $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') of 1232 logs loaded (attempt $count of 150)" + ((count++)) && ((count==150)) && break + sleep 2 +done +if [[ count -le 50 && $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') -ge 1232 ]]; then + green "✅ logstash-vulnwhisperer-2019.03 document count >= 1232" +else + red "❌ TIMED OUT waiting for logstash-vulnwhisperer-2019.03 document count: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq) != 1232" +fi + +# if [[ $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count" | jq '.count') == 1232 ]]; then +# green "✅ Passed: logstash-vulnwhisperer-2019.03 document count == 1232" +# else +# red "❌ Failed: logstash-vulnwhisperer-2019.03 document count == 1232 was: $(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_count") instead" +# ((return_code = return_code + 1)) +# fi + +# Test Nessus plugin_name:Backported Security Patch Detection (FTP) +nessus_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=plugin_name:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:nessus" | jq '.hits.hits[]._source') +if echo $nessus_doc | jq '.risk' | grep -q "None"; then + green "✅ Passed: Nessus risk == None" +else + red "❌ Failed: Nessus risk == None was: $(echo $nessus_doc | jq '.risk') instead" + ((return_code = return_code + 1)) +fi + +# Test Tenable plugin_name:Backported Security Patch Detection (FTP) +tenable_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=plugin_name:%22Backported%20Security%20Patch%20Detection%20(FTP)%22%20AND%20asset:176.28.50.164%20AND%20tags:tenable" | jq '.hits.hits[]._source') +# Test asset +if echo $tenable_doc | jq .asset | grep -q '176.28.50.164'; then + green "✅ Passed: Tenable asset == 176.28.50.164" +else + red "❌ Failed: Tenable asset == 176.28.50.164 was: $(echo $tenable_doc | jq .asset) instead" + ((return_code = return_code + 1)) +fi + +# Test @timestamp +if echo $tenable_doc | jq '.["@timestamp"]' | grep -q '2019-03-30T15:45:44.000Z'; then + green "✅ Passed: Tenable @timestamp == 2019-03-30T15:45:44.000Z" +else + red "❌ Failed: Tenable @timestamp == 2019-03-30T15:45:44.000Z was: $(echo $tenable_doc | jq '.["@timestamp"]') instead" + ((return_code = return_code + 1)) +fi + +# Test Qualys plugin_name:OpenSSL Multiple Remote Security Vulnerabilities +qualys_vuln_doc=$(curl -s "$elasticsearch_url/logstash-vulnwhisperer-2019.03/_search?q=tags:qualys_vuln%20AND%20ip:%22176.28.50.164%22%20AND%20plugin_name:%22OpenSSL%20Multiple%20Remote%20Security%20Vulnerabilities%22%20AND%20port:465" | jq '.hits.hits[]._source') +# Test @timestamp +if echo $qualys_vuln_doc | jq '.["@timestamp"]' | grep -q '2019-03-30T10:17:41.000Z'; then + green "✅ Passed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z" +else + red "❌ Failed: Qualys VM @timestamp == 2019-03-30T10:17:41.000Z was: $(echo $qualys_vuln_doc | jq '.["@timestamp"]') instead" + ((return_code = return_code + 1)) +fi + +# Test @XXXX +if echo $qualys_vuln_doc | jq '.cvss' | grep -q '6.8'; then + green "✅ Passed: Qualys VM cvss == 6.8" +else + red "❌ Failed: Qualys VM cvss == 6.8 was: $(echo $qualys_vuln_doc | jq '.cvss') instead" + ((return_code = return_code + 1)) +fi + +exit $return_code diff --git a/tests/test-vuln_whisperer.sh b/tests/test-vuln_whisperer.sh new file mode 100755 index 0000000..7739e8b --- /dev/null +++ b/tests/test-vuln_whisperer.sh @@ -0,0 +1,97 @@ +#!/usr/bin/env bash + +NORMAL=$(tput sgr0) +GREEN=$(tput setaf 2) +YELLOW=$(tput setaf 3) +RED=$(tput setaf 1) + +function red() { + echo -e "$RED$*$NORMAL" +} + +function green() { + echo -e "$GREEN$*$NORMAL" +} + +function yellow() { + echo -e "$YELLOW$*$NORMAL" +} + +return_code=0 + +TEST_PATH=${TEST_PATH:-"tests/data"} + +yellow "\n*********************************************" +yellow "* Test successful scan download and parsing *" +yellow "*********************************************" +rm -rf /opt/VulnWhisperer/* +if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; then + green "\n✅ Passed: Test successful scan download and parsing" +else + red "\n❌ Failed: Test successful scan download and parsing" + ((return_code = return_code + 1)) +fi + +yellow "\n*********************************************" +yellow "* Test run with no scans to import *" +yellow "*********************************************" +if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; then + green "\n✅ Passed: Test run with no scans to import" +else + red "\n❌ Failed: Test run with no scans to import" + ((return_code = return_code + 1)) +fi + +yellow "\n*********************************************" +yellow "* Test one failed scan *" +yellow "*********************************************" +rm -rf /opt/VulnWhisperer/* +yellow "Removing ${TEST_PATH}/nessus/GET_scans_exports_164_download" +mv "${TEST_PATH}/nessus/GET_scans_exports_164_download"{,.bak} +if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 1 ]]; then + green "\n✅ Passed: Test one failed scan" +else + red "\n❌ Failed: Test one failed scan" + ((return_code = return_code + 1)) +fi + +yellow "\n*********************************************" +yellow "* Test two failed scans *" +yellow "*********************************************" +rm -rf /opt/VulnWhisperer/* +yellow "Removing ${TEST_PATH}/qualys_vuln/scan_1553941061.87241" +mv "${TEST_PATH}/qualys_vuln/scan_1553941061.87241"{,.bak} +if vuln_whisperer -F -c configs/test.ini --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 2 ]]; then + green "\n✅ Passed: Test two failed scans" +else + red "\n❌ Failed: Test two failed scans" + ((return_code = return_code + 1)) +fi + +yellow "\n*********************************************" +yellow "* Test only nessus with one failed scan *" +yellow "*********************************************" +rm -rf /opt/VulnWhisperer/* +if vuln_whisperer -F -c configs/test.ini -s nessus --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 1 ]]; then + green "\n✅ Passed: Test only nessus with one failed scan" +else + red "\n❌ Failed: Test only nessus with one failed scan" + ((return_code = return_code + 1)) +fi + +yellow "*********************************************" +yellow "* Test only Qualys VM with one failed scan *" +yellow "*********************************************" +rm -rf /opt/VulnWhisperer/* +if vuln_whisperer -F -c configs/test.ini -s qualys_vuln --mock --mock_dir "${TEST_PATH}"; [[ $? -eq 1 ]]; then + green "\n✅ Passed: Test only Qualys VM with one failed scan" +else + red "\n❌ Failed: Test only Qualys VM with one failed scan" + ((return_code = return_code + 1)) +fi + +# Restore the removed files +mv "${TEST_PATH}/qualys_vuln/scan_1553941061.87241.bak" "${TEST_PATH}/qualys_vuln/scan_1553941061.87241" +mv "${TEST_PATH}/nessus/GET_scans_exports_164_download.bak" "${TEST_PATH}/nessus/GET_scans_exports_164_download" + +exit $return_code