diff options
author | sergey.galtsev <sergey.galtsev@mongodb.com> | 2021-09-28 22:43:09 +0000 |
---|---|---|
committer | Evergreen Agent <no-reply@evergreen.mongodb.com> | 2021-09-28 23:11:18 +0000 |
commit | e5d32fc7b6811a659ac36f519bf697d776df4849 (patch) | |
tree | 6c2675bad0d3c64daf6c1dea26a738ea242b4921 | |
parent | 9569a71e456821fe24030f59810f384d4a9b8b02 (diff) | |
download | mongo-e5d32fc7b6811a659ac36f519bf697d776df4849.tar.gz |
SERVER-56180 SELinux policy tests
-rw-r--r-- | .gitignore | 8 | ||||
-rw-r--r-- | etc/evergreen.yml | 92 | ||||
-rwxr-xr-x | evergreen/selinux_run_test.sh | 78 | ||||
-rwxr-xr-x | evergreen/selinux_test_executor.sh | 102 | ||||
-rwxr-xr-x | evergreen/selinux_test_setup.sh | 44 | ||||
-rw-r--r-- | jstests/selinux/core.js | 102 | ||||
-rw-r--r-- | jstests/selinux/default.js | 24 | ||||
-rw-r--r-- | jstests/selinux/lib/selinux_base_test.js | 23 |
8 files changed, 473 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore index 2434c53fb6f..acb29394d1c 100644 --- a/.gitignore +++ b/.gitignore @@ -211,3 +211,11 @@ resmoke.ini default.profraw /corpora /corpora-merged + +# RPM build temps +/distsrc.tar +/selinux/tmp +/dst +/rpmbuild +/repo +/rpm/tmp diff --git a/etc/evergreen.yml b/etc/evergreen.yml index dee53d454a2..e3f564cabcd 100644 --- a/etc/evergreen.yml +++ b/etc/evergreen.yml @@ -1441,6 +1441,30 @@ functions: args: - "./src/evergreen/powercycle_setup_host.sh" + "run selinux tests": + - command: host.create + params: + provider: ec2 + distro: ${distro} + timeout_teardown_secs: 86400 # 1 day + - command: host.list + params: + wait: true + timeout_seconds: 900 # 15 min + num_hosts: 1 + path: src/hosts.yml + - *f_expansions_write + - command: subprocess.exec + type: test + params: + binary: bash + args: + - "./src/evergreen/selinux_run_test.sh" + env: + test_list: ${test_list} + user: ec2-user + + ### Process & archive remote EC2 artifacts ### "save powercycle artifacts": &save_powercycle_artifacts command: subprocess.exec @@ -6047,6 +6071,69 @@ tasks: - func: "run powercycle test" timeout_secs: 1800 # 30 minute timeout for no output + +- name: selinux_rhel8_org + tags: [] + depends_on: + - name: package + commands: + - command: manifest.load + - func: "git get project and add git tag" + - *f_expansions_write + - func: "set up venv" + - func: "fetch packages" + - func: "run selinux tests" + vars: + distro: rhel80-selinux + test_list: jstests/selinux/*.js + +- name: selinux_rhel8_enterprise + tags: [] + depends_on: + - name: package + commands: + - command: manifest.load + - func: "git get project and add git tag" + - *f_expansions_write + - func: "set up venv" + - func: "fetch packages" + - func: "run selinux tests" + vars: + distro: rhel80-selinux + test_list: jstests/selinux/*.js src/mongo/db/modules/enterprise/jstests/selinux/*.js + +- name: selinux_rhel7_org + tags: [] + depends_on: + - name: package + commands: + - command: manifest.load + - func: "git get project and add git tag" + - *f_expansions_write + - func: "set up venv" + - func: "fetch packages" + - func: "run selinux tests" + vars: + user: root + distro: rhel76-selinux + test_list: jstests/selinux/*.js + +- name: selinux_rhel7_enterprise + tags: [] + depends_on: + - name: package + commands: + - command: manifest.load + - func: "git get project and add git tag" + - *f_expansions_write + - func: "set up venv" + - func: "fetch packages" + - func: "run selinux tests" + vars: + user: root + distro: rhel76-selinux + test_list: jstests/selinux/*.js src/mongo/db/modules/enterprise/jstests/selinux/*.js + - name: idl_tests tags: [] depends_on: @@ -9136,6 +9223,7 @@ buildvariants: - name: test_packages distros: - ubuntu2004-package + - name: selinux_rhel8_enterprise - name: .publish - &enterprise-rhel-80-64-bit-dynamic-required-template @@ -9830,6 +9918,7 @@ buildvariants: - name: test_packages distros: - ubuntu2004-package + - name: selinux_rhel7_enterprise - name: .publish distros: - rhel70-small @@ -9912,6 +10001,7 @@ buildvariants: - name: test_packages distros: - ubuntu2004-package + - name: selinux_rhel8_enterprise - name: .publish distros: - rhel80-small @@ -9967,6 +10057,7 @@ buildvariants: - name: test_packages distros: - ubuntu2004-package + - name: selinux_rhel8_org - name: .publish distros: - rhel80-small @@ -10149,6 +10240,7 @@ buildvariants: - name: test_packages distros: - ubuntu2004-package + - name: selinux_rhel7_org - name: .publish distros: - rhel70-small diff --git a/evergreen/selinux_run_test.sh b/evergreen/selinux_run_test.sh new file mode 100755 index 00000000000..318d73adbe2 --- /dev/null +++ b/evergreen/selinux_run_test.sh @@ -0,0 +1,78 @@ +#!/bin/bash + +# Notes on how to run this manually: +# - repo must be unpacked into source tree +# +# export ssh_key=$HOME/.ssh/id_rsa +# export hostname=ec2-3-91-230-150.compute-1.amazonaws.com +# export user=ec2-user +# export bypass_prelude=yes +# export workdir="$(dirname $(pwd) | tee /dev/stderr)" +# export src="$(basename $(pwd) | tee /dev/stderr)" +# export test_list='jstests/selinux/*.js' +# export pkg_variant=mongodb-enterprise +# evergreen/selinux_run_test.sh + +set -o errexit + +DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" > /dev/null 2>&1 && pwd)" +if [ "$bypass_prelude" != "yes" ]; then + . "$DIR/prelude.sh" + activate_venv + src="src" +fi + +set -o xtrace + +if [ "$hostname" == "" ]; then + hostname="$(tr -d '"[]{}' < "$workdir"/$src/hosts.yml | cut -d , -f 1 | awk -F : '{print $2}')" +fi + +if [ "$user" == "" ]; then + user=$USER +fi + +host="${user}@${hostname}" +python="${python:-python3}" + +if [ "$ssh_key" == "" ]; then + ssh_key="$workdir/selinux.pem" + "$workdir"/$src/buildscripts/yaml_key_value.py --yamlFile="$workdir"/expansions.yml \ + --yamlKey=__project_aws_ssh_key_value > "$ssh_key" + chmod 600 "$ssh_key" + result="$(openssl rsa -in "$ssh_key" -check -noout | tee /dev/stderr)" + if [ "$result" != "RSA key ok" ]; then + exit 1 + fi +fi + +attempts=0 +connection_attempts=50 + +# Check for remote connectivity +set +o errexit +ssh_options="-i $ssh_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=no" +while ! ssh $ssh_options -o ConnectTimeout=10 "$host" echo "I am working"; do + if [ "$attempts" -ge "$connection_attempts" ]; then exit 1; fi + ((attempts++)) + printf "SSH connection attempt %d/%d failed. Retrying...\n" "$attempts" "$connection_attempts" + sleep 10 +done + +set -o errexit +echo "===> Copying sources to target..." +rsync -ar -e "ssh $ssh_options" \ + --exclude 'tmp' --exclude 'build' --exclude '.*' \ + "$workdir"/$src/* "$host": + +echo "===> Configuring target machine..." +ssh $ssh_options "$host" evergreen/selinux_test_setup.sh + +echo "===> Executing tests..." +list="$( + cd src + for x in $test_list; do echo "$x"; done +)" +for test in $list; do + ssh $ssh_options "$host" evergreen/selinux_test_executor.sh "$test" +done diff --git a/evergreen/selinux_test_executor.sh b/evergreen/selinux_test_executor.sh new file mode 100755 index 00000000000..1ba889a1c80 --- /dev/null +++ b/evergreen/selinux_test_executor.sh @@ -0,0 +1,102 @@ +#!/bin/bash + +set -o errexit +set -o xtrace + +function print() { + echo "$@" >&2 +} + +function monitor_log() { + sed "s!^!mongod| $(date '+%F %H-%M-%S') !" <(sudo --non-interactive tail -f /var/log/mongodb/mongod.log) +} + +TEST_PATH="$1" +if [ ! -f "$TEST_PATH" ]; then + print "No test supplied or test file not found. Run:" + print " $(basename "${BASH_SOURCE[0]}") <path>" + exit 1 +fi + +# test file is even good before going on +if ! mongo --nodb --norc --quiet "$TEST_PATH"; then + print "File $TEST_PATH has syntax errors" + exit 1 +fi + +# stop mongod, zero mongo log, clean up database, set all booleans to off +sudo --non-interactive bash -c ' + systemctl stop mongod + + rm -f /var/log/mongodb/mongod.log + touch /var/log/mongodb/mongod.log + chown mongod /var/log/mongodb/mongod.log + + rm -rf /var/lib/mongo/* + + rm -rf /etc/sysconfig/mongod /etc/mongod + + setsebool mongod_can_connect_snmp off + setsebool mongod_can_connect_ldap off + setsebool mongod_can_use_kerberos off +' + +# create mongo config +mongo --nodb --norc --quiet --eval=' + assert(load("'"$TEST_PATH"'")); + const test = new TestDefinition(); + print(typeof(test.config) === "string" ? test.config : JSON.stringify(test.config, null, 2)); +' | sudo --non-interactive tee /etc/mongod.conf + +# setup +mongo --nodb --norc --quiet --eval=' + assert(load("'"$TEST_PATH"'")); + const test = new TestDefinition(); + jsTest.log("Running setup()"); + test.setup(); +' + +# start log monitor, also kill it on exit +monitor_log & +MONITORPID="$!" +trap "sudo --non-interactive pkill -P $MONITORPID" SIGINT SIGTERM ERR EXIT + +# start mongod and if it won't come up, log SELinux errors +ts="$(date --utc --date='1 seconds ago' '+%x %H:%M:%S')" +tsj="$(date --utc --date='1 seconds ago' +'%Y-%m-%d %H:%M:%S')" +sudo --non-interactive systemctl start mongod \ + && sudo --non-interactive systemctl status mongod || ( + set +o errexit + echo "=== SELinux errors:" + sudo --non-interactive ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts $ts + echo "=== journalctl --unit=mongod:" + sudo --non-interactive journalctl --no-pager --since="$tsj" --unit=mongod --unit=systemd --catalog + echo "=== /var/log/mongodb/mongod.log:" + sudo --non-interactive cat /var/log/mongodb/mongod.log + echo "==== FAIL: mongod service was not started successfully" + exit 1 +) + +# run test and teardown +mongo --norc --gssapiServiceName=mockservice --eval=' + assert(load("'"$TEST_PATH"'")); + // name is such to prevent collisions + const test_812de7ce = new TestDefinition(); + try { + jsTest.log("Running test"); + test_812de7ce.run(); + } finally { + test_812de7ce.teardown(); + } +' || ( + echo "==== FAIL: test returned result: $?" + echo "=== SELinux errors:" + set +o errexit + sudo --non-interactive ausearch -m AVC,USER_AVC,SELINUX_ERR,USER_SELINUX_ERR -ts $ts + echo "=== /var/log/mongodb/mongod.log:" + sudo --non-interactive cat /var/log/mongodb/mongod.log + exit 1 +) + +set +o xtrace +echo "SUCCESS: $TEST_PATH" diff --git a/evergreen/selinux_test_setup.sh b/evergreen/selinux_test_setup.sh new file mode 100755 index 00000000000..ef2e70df2a9 --- /dev/null +++ b/evergreen/selinux_test_setup.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# This script is loaded on the target machine, which is running tests +# Purpose: install mongod and shell from packages + +set -o xtrace +set -o errexit + +function apply_selinux_policy() { + echo "==== Applying SELinux policy now" + rm -rf mongodb-selinux + git clone https://github.com/mongodb/mongodb-selinux + cd mongodb-selinux + make + sudo make install +} + +# on evergreen images /tmp is usually linked to /data/tmp, which interferes +# with selinux, as it does not recognize it as tmp_t domain +if [ -L /tmp ]; then + sudo --non-interactive rm /tmp + sudo --non-interactive mkdir /tmp + sudo --non-interactive systemctl start tmp.mount +fi + +# selinux policy should work both when applied before and after install +# we will randomly apply it before or after installation is completed +SEORDER="$(($RANDOM % 2))" +if [ "$SEORDER" == "0" ]; then + apply_selinux_policy +fi + +# install shell using yum, so that dependencies are pulled +pkg="$(find "$HOME"/repo -name 'mongodb-*-shell-*.x86_64.rpm' | tee /dev/stderr)" +sudo --non-interactive yum install --assumeyes "$pkg" \ + || if [ "$?" -gt "1" ]; then exit 1; fi # exit code 1 is OK + +pkg="$(find "$HOME"/repo -name 'mongodb-*-server-*.x86_64.rpm' | tee /dev/stderr)" +sudo --non-interactive rpm --install --verbose --verbose --hash --nodeps "$pkg" \ + || if [ "$?" -gt "1" ]; then exit 1; fi # exit code 1 is OK + +if [ "$SEORDER" == "1" ]; then + apply_selinux_policy +fi diff --git a/jstests/selinux/core.js b/jstests/selinux/core.js new file mode 100644 index 00000000000..b4ac8652af7 --- /dev/null +++ b/jstests/selinux/core.js @@ -0,0 +1,102 @@ + +'use strict'; + +load('jstests/selinux/lib/selinux_base_test.js'); + +class TestDefinition extends SelinuxBaseTest { + get config() { + return { + "systemLog": + {"destination": "file", "logAppend": true, "path": "/var/log/mongodb/mongod.log"}, + "storage": {"dbPath": "/var/lib/mongo", "journal": {"enabled": true}}, + "processManagement": { + "fork": true, + "pidFilePath": "/var/run/mongodb/mongod.pid", + "timeZoneInfo": "/usr/share/zoneinfo" + }, + "net": {"port": 27017, "bindIp": "127.0.0.1"} + }; + } + + run() { + let dirs = ["jstests/core", "jstests/core_standalone"]; + + // Tests in jstests/core weren't specifically made to pass in this very scenario, so we + // will not be fixing what is not working, and instead exclude them from running as + // "known" to not work + const exclude = new Set([ + "jstests/core/api_version_parameters.js", + "jstests/core/api_version_test_expression.js", + "jstests/core/basic6.js", + "jstests/core/capped_empty.js", + "jstests/core/capped_update.js", + "jstests/core/check_shard_index.js", + "jstests/core/collection_truncate.js", + "jstests/core/commands_namespace_parsing.js", + "jstests/core/comment_field.js", + "jstests/core/compound_index_max_fields.js", + "jstests/core/crud_ops_do_not_throw_locktimeout.js", + "jstests/core/currentop_cursors.js", + "jstests/core/currentop_shell.js", + "jstests/core/currentop_waiting_for_latch.js", + "jstests/core/datasize2.js", + "jstests/core/doc_validation_options.js", + "jstests/core/double_decimal_compare.js", + "jstests/core/drop_collection.js", + "jstests/core/explain_uuid.js", + "jstests/core/failcommand_failpoint.js", + "jstests/core/geo_near_point_query.js", + "jstests/core/getlog2.js", + "jstests/core/hash.js", + "jstests/core/indexj.js", + "jstests/core/jssymbol.js", + "jstests/core/latch_analyzer.js", + "jstests/core/list_all_sessions.js", + "jstests/core/list_sessions.js", + "jstests/core/logprocessdetails.js", + "jstests/core/mr_killop.js", + "jstests/core/profile_hide_index.js", + "jstests/core/rename_collection_capped.js", + "jstests/core/resume_query.js", + "jstests/core/splitvector.js", + "jstests/core/sort_with_update_between_getmores.js", + "jstests/core/stages_and_hash.js", + "jstests/core/stages_and_sorted.js", + "jstests/core/stages_collection_scan.js", + "jstests/core/stages_delete.js", + "jstests/core/stages_fetch.js", + "jstests/core/stages_ixscan.js", + "jstests/core/stages_limit_skip.js", + "jstests/core/stages_mergesort.js", + "jstests/core/stages_or.js", + "jstests/core/type8.js", + "jstests/core/validate_db_metadata_command.js", + "jstests/core/version_api_list_commands_verification.js", + "jstests/core/wildcard_index_distinct_scan.js", + "jstests/core/wildcard_index_projection.js" + ]); + + for (let id = 0; id < dirs.length; ++id) { + const dir = dirs[id]; + jsTest.log("Running tests in " + dir); + + const all_tests = ls(dir).filter(d => !d.endsWith("/") && !exclude.has(d)).sort(); + assert(all_tests); + assert(all_tests.length); + + for (let i = 0; i < all_tests.length; ++i) { + let t = all_tests[i]; + if (t.endsWith("/")) { + continue; + } + jsTest.log("Running test: " + t); + if (!load(t)) { + throw ("failed to load test " + t); + } + jsTest.log("Successful test: " + t); + } + } + + jsTest.log("code test suite ran successfully"); + } +} diff --git a/jstests/selinux/default.js b/jstests/selinux/default.js new file mode 100644 index 00000000000..9252e84d9b7 --- /dev/null +++ b/jstests/selinux/default.js @@ -0,0 +1,24 @@ +// This test does not run any code. As long as mongod is +// up and running, it is successful + +'use strict'; + +load('jstests/selinux/lib/selinux_base_test.js'); + +class TestDefinition extends SelinuxBaseTest { + get config() { + return cat("rpm/mongod.conf"); + } + + run() { + // The only things we are verifying here: + // - that we are connected + // - that process is running in correct SELinux context + + assert(db); + assert.eq(0, + run("bash", "-c", "ps -efZ | grep -P 'system_u:system_r:mongod_t:s0[ ]+mongod'")); + + jsTest.log("success"); + } +} diff --git a/jstests/selinux/lib/selinux_base_test.js b/jstests/selinux/lib/selinux_base_test.js new file mode 100644 index 00000000000..a5cccd451fc --- /dev/null +++ b/jstests/selinux/lib/selinux_base_test.js @@ -0,0 +1,23 @@ +'use strict'; + +class SelinuxBaseTest { + get config() { + return {}; + } + + // Notice: private definitions, e.g.: #sudo() are not + // recognized by js linter, so leaving this declaration public + sudo(script) { + return run("sudo", "--non-interactive", "bash", "-c", script); + } + + setup() { + } + + teardown() { + } + + run() { + assert("override this function"); + } +} |