From 216da0b733dbed9aad9b2ab92ac75bcb906fd7ee Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Sat, 1 Jun 2019 23:09:26 -0700 Subject: Add tooling for Go-compatible branch. The WORKSPACE go_repositories can be generated from a standard go.mod file. Add the necessary gazelle hooks to do so, and include a test that sanity checks there are no changes. This go.mod file will be used in a subsequent commit to generate a go gettable branch of the repository. This commit also adds a tools/go_branch.sh script, which given an existing go branch in the repository, will add an additional synthetic change to the branch bringing it up-to-date with HEAD. As a final step, a cloudbuild script is included, which can be used to automate the process for every change pushed to the repository. This may be used after an initial go branch is pushed, but this is manual process. PiperOrigin-RevId: 251095016 --- tools/go_branch.sh | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 76 insertions(+) create mode 100755 tools/go_branch.sh (limited to 'tools') diff --git a/tools/go_branch.sh b/tools/go_branch.sh new file mode 100755 index 000000000..8ea6a6d8d --- /dev/null +++ b/tools/go_branch.sh @@ -0,0 +1,76 @@ +#!/bin/bash + +# Copyright 2019 The gVisor Authors. +# +# 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 +# +# http://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. + +set -eo pipefail + +# Discovery the package name from the go.mod file. +declare -r gomod="$(pwd)/go.mod" +declare -r module=$(cat "${gomod}" | grep -E "^module" | cut -d' ' -f2) + +# Check that gopath has been built. +declare -r gopath_dir="$(pwd)/bazel-bin/gopath/src/${module}" +if ! [ -d "${gopath_dir}" ]; then + echo "No gopath directory found; build the :gopath target." >&2 + exit 1 +fi + +# Create a temporary working directory, and ensure that this directory and all +# subdirectories are cleaned up upon exit. +declare -r tmp_dir=$(mktemp -d) +finish() { + cd # Leave tmp_dir. + rm -rf "${tmp_dir}" +} +trap finish EXIT + +# Record the current working commit. +declare -r head=$(git describe --always) + +# We expect to have an existing go branch that we will use as the basis for +# this commit. That branch may be empty, but it must exist. +declare -r go_branch=$(git show-ref --hash origin/go) + +# Clone the current repository to the temporary directory, and check out the +# current go_branch directory. We move to the new repository for convenience. +declare -r repo_orig="$(pwd)" +declare -r repo_new="${tmp_dir}/repository" +git clone . "${repo_new}" +cd "${repo_new}" + +# Setup the repository and checkout the branch. +git config user.email "gvisor-bot@google.com" +git config user.name "gVisor bot" +git fetch origin "${go_branch}" +git checkout -b go "${go_branch}" + +# Start working on a merge commit that combines the previous history with the +# current history. Note that we don't actually want any changes yet. +git merge --allow-unrelated-histories --no-commit --strategy ours ${head} + +# Sync the entire gopath_dir and go.mod. +rsync --recursive --verbose --delete --exclude .git --exclude README.md -L "${gopath_dir}/" . +cp "${gomod}" . + +# There are a few solitary files that can get left behind due to the way bazel +# constructs the gopath target. Note that we don't find all Go files here +# because they may correspond to unused templates, etc. +cp "${repo_orig}"/runsc/*.go runsc/ + +# Update the current working set and commit. +git add . && git commit -m "Merge ${head} (automated)" + +# Push the branch back to the original repository. +git remote add orig "${repo_orig}" && git push -f orig go:go -- cgit v1.2.3 From 7436ea247bc946b36a7e5e6ca6019796ef76d85c Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Tue, 4 Jun 2019 11:06:13 -0700 Subject: Fix Kokoro revision and 'go get usage' As a convenience for debugging, also factor the scripts such that can be run without Kokoro. In the future, this may be used to add additional presubmit hooks that run without Kokoro. PiperOrigin-RevId: 251474868 --- kokoro/run_build.sh | 43 +-------- kokoro/run_tests.sh | 266 +------------------------------------------------- tools/run_build.sh | 44 +++++++++ tools/run_tests.sh | 273 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 319 insertions(+), 307 deletions(-) mode change 100755 => 120000 kokoro/run_build.sh mode change 100755 => 120000 kokoro/run_tests.sh create mode 100755 tools/run_build.sh create mode 100755 tools/run_tests.sh (limited to 'tools') diff --git a/kokoro/run_build.sh b/kokoro/run_build.sh deleted file mode 100755 index 63fffda48..000000000 --- a/kokoro/run_build.sh +++ /dev/null @@ -1,42 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# 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 -# -# http://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. - -# Fail on any error. -set -e -# Display commands to stderr. -set -x - -# Install the latest version of Bazel. -use_bazel.sh latest - -# Log the bazel path and version. -which bazel -bazel version - -cd git/repo - -# Build runsc. -bazel build //runsc - -# Move the runsc binary into "latest" directory, and also a directory with the -# current date. -latest_dir="${KOKORO_ARTIFACTS_DIR}"/latest -today_dir="${KOKORO_ARTIFACTS_DIR}"/"$(date -Idate)" -mkdir -p "${latest_dir}" "${today_dir}" -cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc "${latest_dir}" -sha512sum "${latest_dir}"/runsc | awk '{print $1 " runsc"}' > "${latest_dir}"/runsc.sha512 -cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc "${today_dir}" -sha512sum "${today_dir}"/runsc | awk '{print $1 " runsc"}' > "${today_dir}"/runsc.sha512 diff --git a/kokoro/run_build.sh b/kokoro/run_build.sh new file mode 120000 index 000000000..9deafe9bb --- /dev/null +++ b/kokoro/run_build.sh @@ -0,0 +1 @@ +../tools/run_build.sh \ No newline at end of file diff --git a/kokoro/run_tests.sh b/kokoro/run_tests.sh deleted file mode 100755 index 6ff72ce1d..000000000 --- a/kokoro/run_tests.sh +++ /dev/null @@ -1,265 +0,0 @@ -#!/bin/bash - -# Copyright 2018 The gVisor Authors. -# -# 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 -# -# http://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. - -# Fail on any error. Treat unset variables as error. Print commands as executed. -set -eux - - -################### -# GLOBAL ENV VARS # -################### - -readonly WORKSPACE_DIR="${PWD}/git/repo" - -# Used to configure RBE. -readonly CLOUD_PROJECT_ID="gvisor-rbe" -readonly RBE_PROJECT_ID="projects/${CLOUD_PROJECT_ID}/instances/default_instance" - -# Random runtime name to avoid collisions. -readonly RUNTIME="runsc_test_$((RANDOM))" - -# Packages that will be built and tested. -readonly BUILD_PACKAGES=("//...") -readonly TEST_PACKAGES=("//pkg/..." "//runsc/..." "//tools/...") - -####################### -# BAZEL CONFIGURATION # -####################### - -# Install the latest version of Bazel, and log the location and version. -use_bazel.sh latest -which bazel -bazel version - -# Load the kvm module -sudo -n -E modprobe kvm - -# General Bazel build/test flags. -BAZEL_BUILD_FLAGS=( - "--show_timestamps" - "--test_output=errors" - "--keep_going" - "--verbose_failures=true" -) - -# Bazel build/test for RBE, a super-set of BAZEL_BUILD_FLAGS. -BAZEL_BUILD_RBE_FLAGS=( - "${BAZEL_BUILD_FLAGS[@]}" - "--config=remote" - "--project_id=${CLOUD_PROJECT_ID}" - "--remote_instance_name=${RBE_PROJECT_ID}" - "--auth_credentials=${KOKORO_BAZEL_AUTH_CREDENTIAL}" -) - -#################### -# Helper Functions # -#################### - -sanity_checks() { - cd ${WORKSPACE_DIR} - bazel run //:gazelle -- update-repos -from_file=go.mod - git diff --exit-code WORKSPACE -} - -build_everything() { - FLAVOR="${1}" - - cd ${WORKSPACE_DIR} - bazel build \ - -c "${FLAVOR}" "${BAZEL_BUILD_RBE_FLAGS[@]}" \ - "${BUILD_PACKAGES[@]}" -} - -# Run simple tests runs the tests that require no special setup or -# configuration. -run_simple_tests() { - cd ${WORKSPACE_DIR} - bazel test \ - "${BAZEL_BUILD_FLAGS[@]}" \ - "${TEST_PACKAGES[@]}" -} - -install_runtime() { - cd ${WORKSPACE_DIR} - sudo -n ${WORKSPACE_DIR}/runsc/test/install.sh --runtime ${RUNTIME} -} - -# Install dependencies for the crictl tests. -install_crictl_test_deps() { - # Install containerd. - sudo -n -E apt-get update - sudo -n -E apt-get install -y btrfs-tools libseccomp-dev - # go get will exit with a status of 1 despite succeeding, so ignore errors. - go get -d github.com/containerd/containerd || true - cd ${GOPATH}/src/github.com/containerd/containerd - git checkout v1.2.2 - make - sudo -n -E make install - - # Install crictl. - # go get will exit with a status of 1 despite succeeding, so ignore errors. - go get -d github.com/kubernetes-sigs/cri-tools || true - cd ${GOPATH}/src/github.com/kubernetes-sigs/cri-tools - git checkout tags/v1.11.0 - make - sudo -n -E make install - - # Install gvisor-containerd-shim. - local latest=/tmp/gvisor-containerd-shim-latest - local shim_path=/tmp/gvisor-containerd-shim - wget --no-verbose https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim/latest -O ${latest} - wget --no-verbose https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim/gvisor-containerd-shim-$(cat ${latest}) -O ${shim_path} - chmod +x ${shim_path} - sudo -n -E mv ${shim_path} /usr/local/bin - - # Configure containerd-shim. - local shim_config_path=/etc/containerd - local shim_config_tmp_path=/tmp/gvisor-containerd-shim.toml - sudo -n -E mkdir -p ${shim_config_path} - cat > ${shim_config_tmp_path} <<-EOF - runc_shim = "/usr/local/bin/containerd-shim" - - [runsc_config] - debug = "true" - debug-log = "/tmp/runsc-logs/" - strace = "true" - file-access = "shared" -EOF - sudo mv ${shim_config_tmp_path} ${shim_config_path} - - # Configure CNI. - sudo -n -E env PATH=${PATH} ${GOPATH}/src/github.com/containerd/containerd/script/setup/install-cni -} - -# Run the tests that require docker. -run_docker_tests() { - cd ${WORKSPACE_DIR} - - # Run tests with a default runtime (runc). - bazel test \ - "${BAZEL_BUILD_FLAGS[@]}" \ - --test_env=RUNSC_RUNTIME="" \ - --test_output=all \ - //runsc/test/image:image_test - - # These names are used to exclude tests not supported in certain - # configuration, e.g. save/restore not supported with hostnet. - declare -a variations=("" "-kvm" "-hostnet" "-overlay") - for v in "${variations[@]}"; do - # Run runsc tests with docker that are tagged manual. - bazel test \ - "${BAZEL_BUILD_FLAGS[@]}" \ - --test_env=RUNSC_RUNTIME="${RUNTIME}${v}" \ - --test_output=all \ - //runsc/test/image:image_test \ - //runsc/test/integration:integration_test - done -} - -# Run the tests that require root. -run_root_tests() { - cd ${WORKSPACE_DIR} - bazel build //runsc/test/root:root_test - local root_test=$(find -L ./bazel-bin/ -executable -type f -name root_test | grep __main__) - if [[ ! -f "${root_test}" ]]; then - echo "root_test executable not found" - exit 1 - fi - sudo -n -E RUNSC_RUNTIME="${RUNTIME}" RUNSC_EXEC=/tmp/"${RUNTIME}"/runsc ${root_test} -} - -# Run syscall unit tests. -run_syscall_tests() { - cd ${WORKSPACE_DIR} - bazel test "${BAZEL_BUILD_RBE_FLAGS[@]}" \ - --test_tag_filters=runsc_ptrace //test/syscalls/... -} - -run_runsc_do_tests() { - local runsc=$(find bazel-bin/runsc -type f -executable -name "runsc" | head -n1) - - # run runsc do without root privileges. - unshare -Ur ${runsc} --network=none --TESTONLY-unsafe-nonroot do true - unshare -Ur ${runsc} --TESTONLY-unsafe-nonroot --network=host do --netns=false true - - # run runsc do with root privileges. - sudo -n -E ${runsc} do true -} - -# Find and rename all test xml and log files so that Sponge can pick them up. -# XML files must be named sponge_log.xml, and log files must be named -# sponge_log.log. We move all such files into KOKORO_ARTIFACTS_DIR, in a -# subdirectory named with the test name. -upload_test_artifacts() { - cd ${WORKSPACE_DIR} - find -L "bazel-testlogs" -name "test.xml" -o -name "test.log" -o -name "outputs.zip" | - tar --create --files-from - --transform 's/test\./sponge_log./' | - tar --extract --directory ${KOKORO_ARTIFACTS_DIR} - if [[ -d "/tmp/${RUNTIME}/logs" ]]; then - tar --create --gzip "--file=${KOKORO_ARTIFACTS_DIR}/runsc-logs.tar.gz" -C /tmp/ ${RUNTIME}/logs - fi -} - -# Finish runs at exit, even in the event of an error, and uploads all test -# artifacts. -finish() { - # Grab the last exit code, we will return it. - local exit_code=${?} - upload_test_artifacts - exit ${exit_code} -} - -# Run bazel in a docker container -build_in_docker() { - cd ${WORKSPACE_DIR} - bazel clean - bazel shutdown - make - make runsc - make bazel-shutdown -} - -######## -# MAIN # -######## - -main() { - # Register finish to run at exit. - trap finish EXIT - - # Build and run the simple tests. - sanity_checks - build_everything opt - run_simple_tests - - # So far so good. Install more deps and run the integration tests. - install_runtime - install_crictl_test_deps - run_docker_tests - run_root_tests - - run_syscall_tests - run_runsc_do_tests - - # Build other flavors too. - build_everything dbg - - build_in_docker - # No need to call "finish" here, it will happen at exit. -} - -# Kick it off. -main diff --git a/kokoro/run_tests.sh b/kokoro/run_tests.sh new file mode 120000 index 000000000..931cd2622 --- /dev/null +++ b/kokoro/run_tests.sh @@ -0,0 +1 @@ +../tools/run_tests.sh \ No newline at end of file diff --git a/tools/run_build.sh b/tools/run_build.sh new file mode 100755 index 000000000..b6b446690 --- /dev/null +++ b/tools/run_build.sh @@ -0,0 +1,44 @@ +#!/bin/bash + +# Copyright 2018 The gVisor Authors. +# +# 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 +# +# http://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. + +# Fail on any error. +set -e +# Display commands to stderr. +set -x + +# Install the latest version of Bazel and log the version. +(which use_bazel.sh && use_bazel.sh latest) || which bazel +bazel version + +# Switch into the workspace and checkout the appropriate commit. +if [[ -v KOKORO_GIT_COMMIT ]]; then + cd git/repo && git checkout "${KOKORO_GIT_COMMIT}" +fi + +# Build runsc. +bazel build //runsc + +# Move the runsc binary into "latest" directory, and also a directory with the +# current date. +if [[ -v KOKORO_ARTIFACTS_DIR ]]; then + latest_dir="${KOKORO_ARTIFACTS_DIR}"/latest + today_dir="${KOKORO_ARTIFACTS_DIR}"/"$(date -Idate)" + mkdir -p "${latest_dir}" "${today_dir}" + cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc "${latest_dir}" + sha512sum "${latest_dir}"/runsc | awk '{print $1 " runsc"}' > "${latest_dir}"/runsc.sha512 + cp bazel-bin/runsc/linux_amd64_pure_stripped/runsc "${today_dir}" + sha512sum "${today_dir}"/runsc | awk '{print $1 " runsc"}' > "${today_dir}"/runsc.sha512 +fi diff --git a/tools/run_tests.sh b/tools/run_tests.sh new file mode 100755 index 000000000..c6e97dc95 --- /dev/null +++ b/tools/run_tests.sh @@ -0,0 +1,273 @@ +#!/bin/bash + +# Copyright 2018 The gVisor Authors. +# +# 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 +# +# http://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. + +# Fail on any error. Treat unset variables as error. Print commands as executed. +set -eux + +################### +# GLOBAL ENV VARS # +################### + +if [[ -v KOKORO_GIT_COMMIT ]]; then + readonly WORKSPACE_DIR="${PWD}/git/repo" +else + readonly WORKSPACE_DIR="${PWD}" +fi + +# Used to configure RBE. +readonly CLOUD_PROJECT_ID="gvisor-rbe" +readonly RBE_PROJECT_ID="projects/${CLOUD_PROJECT_ID}/instances/default_instance" + +# Random runtime name to avoid collisions. +readonly RUNTIME="runsc_test_$((RANDOM))" + +# Packages that will be built and tested. +readonly BUILD_PACKAGES=("//...") +readonly TEST_PACKAGES=("//pkg/..." "//runsc/..." "//tools/...") + +####################### +# BAZEL CONFIGURATION # +####################### + +# Install the latest version of Bazel and log the version. +(which use_bazel.sh && use_bazel.sh latest) || which bazel +bazel version + +# Checkout the appropriate commit. +if [[ -v KOKORO_GIT_COMMIT ]]; then + (cd "${WORKSPACE_DIR}" && git checkout "${KOKORO_GIT_COMMIT}") +fi + +# Load the kvm module. +sudo -n -E modprobe kvm + +# General Bazel build/test flags. +BAZEL_BUILD_FLAGS=( + "--show_timestamps" + "--test_output=errors" + "--keep_going" + "--verbose_failures=true" +) + +# Bazel build/test for RBE, a super-set of BAZEL_BUILD_FLAGS. +BAZEL_BUILD_RBE_FLAGS=( + "${BAZEL_BUILD_FLAGS[@]}" + "--config=remote" + "--project_id=${CLOUD_PROJECT_ID}" + "--remote_instance_name=${RBE_PROJECT_ID}" +) +if [[ -v KOKORO_BAZEL_AUTH_CREDENTIAL ]]; then + BAZEL_BUILD_RBE_FLAGS=( + "${BAZEL_BUILD_RBE_FLAGS[@]}" + "--auth_credentials=${KOKORO_BAZEL_AUTH_CREDENTIAL}" + ) +fi + +#################### +# Helper Functions # +#################### + +sanity_checks() { + cd ${WORKSPACE_DIR} + bazel run //:gazelle -- update-repos -from_file=go.mod + git diff --exit-code WORKSPACE +} + +build_everything() { + FLAVOR="${1}" + + cd ${WORKSPACE_DIR} + bazel build \ + -c "${FLAVOR}" "${BAZEL_BUILD_RBE_FLAGS[@]}" \ + "${BUILD_PACKAGES[@]}" +} + +# Run simple tests runs the tests that require no special setup or +# configuration. +run_simple_tests() { + cd ${WORKSPACE_DIR} + bazel test \ + "${BAZEL_BUILD_FLAGS[@]}" \ + "${TEST_PACKAGES[@]}" +} + +install_runtime() { + cd ${WORKSPACE_DIR} + sudo -n ${WORKSPACE_DIR}/runsc/test/install.sh --runtime ${RUNTIME} +} + +# Install dependencies for the crictl tests. +install_crictl_test_deps() { + sudo -n -E apt-get update + sudo -n -E apt-get install -y btrfs-tools libseccomp-dev + + # Install containerd. + [[ -d containerd ]] || git clone https://github.com/containerd/containerd + (cd containerd && git checkout v1.2.2 && make && sudo -n -E make install) + + # Install crictl. + [[ -d cri-tools ]] || git clone https://github.com/kubernetes-sigs/cri-tools + (cd cri-tools && git checkout tags/v1.11.0 && make && sudo -n -E make install) + + # Install gvisor-containerd-shim. + local latest=/tmp/gvisor-containerd-shim-latest + local shim_path=/tmp/gvisor-containerd-shim + wget --no-verbose https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim/latest -O ${latest} + wget --no-verbose https://storage.googleapis.com/cri-containerd-staging/gvisor-containerd-shim/gvisor-containerd-shim-$(cat ${latest}) -O ${shim_path} + chmod +x ${shim_path} + sudo -n -E mv ${shim_path} /usr/local/bin + + # Configure containerd-shim. + local shim_config_path=/etc/containerd + local shim_config_tmp_path=/tmp/gvisor-containerd-shim.toml + sudo -n -E mkdir -p ${shim_config_path} + cat > ${shim_config_tmp_path} <<-EOF + runc_shim = "/usr/local/bin/containerd-shim" + + [runsc_config] + debug = "true" + debug-log = "/tmp/runsc-logs/" + strace = "true" + file-access = "shared" +EOF + sudo mv ${shim_config_tmp_path} ${shim_config_path} + + # Configure CNI. + sudo -n -E env PATH=${PATH} containerd/script/setup/install-cni +} + +# Run the tests that require docker. +run_docker_tests() { + cd ${WORKSPACE_DIR} + + # Run tests with a default runtime (runc). + bazel test \ + "${BAZEL_BUILD_FLAGS[@]}" \ + --test_env=RUNSC_RUNTIME="" \ + --test_output=all \ + //runsc/test/image:image_test + + # These names are used to exclude tests not supported in certain + # configuration, e.g. save/restore not supported with hostnet. + declare -a variations=("" "-kvm" "-hostnet" "-overlay") + for v in "${variations[@]}"; do + # Run runsc tests with docker that are tagged manual. + bazel test \ + "${BAZEL_BUILD_FLAGS[@]}" \ + --test_env=RUNSC_RUNTIME="${RUNTIME}${v}" \ + --test_output=all \ + //runsc/test/image:image_test \ + //runsc/test/integration:integration_test + done +} + +# Run the tests that require root. +run_root_tests() { + cd ${WORKSPACE_DIR} + bazel build //runsc/test/root:root_test + local root_test=$(find -L ./bazel-bin/ -executable -type f -name root_test | grep __main__) + if [[ ! -f "${root_test}" ]]; then + echo "root_test executable not found" + exit 1 + fi + sudo -n -E RUNSC_RUNTIME="${RUNTIME}" RUNSC_EXEC=/tmp/"${RUNTIME}"/runsc ${root_test} +} + +# Run syscall unit tests. +run_syscall_tests() { + cd ${WORKSPACE_DIR} + bazel test "${BAZEL_BUILD_RBE_FLAGS[@]}" \ + --test_tag_filters=runsc_ptrace //test/syscalls/... +} + +run_runsc_do_tests() { + local runsc=$(find bazel-bin/runsc -type f -executable -name "runsc" | head -n1) + + # run runsc do without root privileges. + unshare -Ur ${runsc} --network=none --TESTONLY-unsafe-nonroot do true + unshare -Ur ${runsc} --TESTONLY-unsafe-nonroot --network=host do --netns=false true + + # run runsc do with root privileges. + sudo -n -E ${runsc} do true +} + +# Find and rename all test xml and log files so that Sponge can pick them up. +# XML files must be named sponge_log.xml, and log files must be named +# sponge_log.log. We move all such files into KOKORO_ARTIFACTS_DIR, in a +# subdirectory named with the test name. +upload_test_artifacts() { + # Skip if no kokoro directory. + [[ -v KOKORO_ARTIFACTS_DIR ]] || return + + cd ${WORKSPACE_DIR} + find -L "bazel-testlogs" -name "test.xml" -o -name "test.log" -o -name "outputs.zip" | + tar --create --files-from - --transform 's/test\./sponge_log./' | + tar --extract --directory ${KOKORO_ARTIFACTS_DIR} + if [[ -d "/tmp/${RUNTIME}/logs" ]]; then + tar --create --gzip "--file=${KOKORO_ARTIFACTS_DIR}/runsc-logs.tar.gz" -C /tmp/ ${RUNTIME}/logs + fi +} + +# Finish runs at exit, even in the event of an error, and uploads all test +# artifacts. +finish() { + # Grab the last exit code, we will return it. + local exit_code=${?} + upload_test_artifacts + exit ${exit_code} +} + +# Run bazel in a docker container +build_in_docker() { + cd ${WORKSPACE_DIR} + bazel clean + bazel shutdown + make + make runsc + make bazel-shutdown +} + +######## +# MAIN # +######## + +main() { + # Register finish to run at exit. + trap finish EXIT + + # Build and run the simple tests. + sanity_checks + build_everything opt + run_simple_tests + + # So far so good. Install more deps and run the integration tests. + install_runtime + install_crictl_test_deps + run_docker_tests + run_root_tests + + run_syscall_tests + run_runsc_do_tests + + # Build other flavors too. + build_everything dbg + + build_in_docker + # No need to call "finish" here, it will happen at exit. +} + +# Kick it off. +main -- cgit v1.2.3 From 6f92038ce0d2062c3dfd84fe65141ee09deeabfc Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Tue, 4 Jun 2019 14:42:25 -0700 Subject: Use github directory if it exists. Unfortunately, kokoro names the top-level directory per the SCM type. This means there's no way to make the job names match; we simply need to probe for the existence of the correct directory. PiperOrigin-RevId: 251519409 --- tools/run_build.sh | 8 +++++--- tools/run_tests.sh | 9 +++------ 2 files changed, 8 insertions(+), 9 deletions(-) (limited to 'tools') diff --git a/tools/run_build.sh b/tools/run_build.sh index b6b446690..d49a1d4be 100755 --- a/tools/run_build.sh +++ b/tools/run_build.sh @@ -23,9 +23,11 @@ set -x (which use_bazel.sh && use_bazel.sh latest) || which bazel bazel version -# Switch into the workspace and checkout the appropriate commit. -if [[ -v KOKORO_GIT_COMMIT ]]; then - cd git/repo && git checkout "${KOKORO_GIT_COMMIT}" +# Switch into the workspace. +if [[ -v KOKORO_GIT_COMMIT ]] && [[ -d git/repo ]]; then + cd git/repo +elif [[ -v KOKORO_GIT_COMMIT ]] && [[ -d github/repo ]]; then + cd github/repo fi # Build runsc. diff --git a/tools/run_tests.sh b/tools/run_tests.sh index c6e97dc95..dc282c142 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -21,8 +21,10 @@ set -eux # GLOBAL ENV VARS # ################### -if [[ -v KOKORO_GIT_COMMIT ]]; then +if [[ -v KOKORO_GIT_COMMIT ]] && [[ -d git/repo ]]; then readonly WORKSPACE_DIR="${PWD}/git/repo" +elif [[ -v KOKORO_GIT_COMMIT ]] && [[ -d github/repo ]]; then + readonly WORKSPACE_DIR="${PWD}/github/repo" else readonly WORKSPACE_DIR="${PWD}" fi @@ -46,11 +48,6 @@ readonly TEST_PACKAGES=("//pkg/..." "//runsc/..." "//tools/...") (which use_bazel.sh && use_bazel.sh latest) || which bazel bazel version -# Checkout the appropriate commit. -if [[ -v KOKORO_GIT_COMMIT ]]; then - (cd "${WORKSPACE_DIR}" && git checkout "${KOKORO_GIT_COMMIT}") -fi - # Load the kvm module. sudo -n -E modprobe kvm -- cgit v1.2.3 From cecb71dc37a77d8e4e88cdfada92a37a72c67602 Mon Sep 17 00:00:00 2001 From: Adin Scannell Date: Tue, 4 Jun 2019 23:08:20 -0700 Subject: Building containerd with go modules is broken, use GOPATH. PiperOrigin-RevId: 251583707 --- tools/run_tests.sh | 30 ++++++++++++++++++++++-------- 1 file changed, 22 insertions(+), 8 deletions(-) (limited to 'tools') diff --git a/tools/run_tests.sh b/tools/run_tests.sh index dc282c142..b35d2e4b8 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -106,18 +106,31 @@ install_runtime() { sudo -n ${WORKSPACE_DIR}/runsc/test/install.sh --runtime ${RUNTIME} } +install_helper() { + PACKAGE="${1}" + TAG="${2}" + GOPATH="${3}" + + # Clone the repository. + mkdir -p "${GOPATH}"/src/$(dirname "${PACKAGE}") && \ + git clone https://"${PACKAGE}" "${GOPATH}"/src/"${PACKAGE}" + + # Checkout and build the repository. + (cd "${GOPATH}"/src/"${PACKAGE}" && \ + git checkout "${TAG}" && \ + GOPATH="${GOPATH}" make && \ + sudo -n -E env GOPATH="${GOPATH}" make install) +} + # Install dependencies for the crictl tests. install_crictl_test_deps() { sudo -n -E apt-get update sudo -n -E apt-get install -y btrfs-tools libseccomp-dev - # Install containerd. - [[ -d containerd ]] || git clone https://github.com/containerd/containerd - (cd containerd && git checkout v1.2.2 && make && sudo -n -E make install) - - # Install crictl. - [[ -d cri-tools ]] || git clone https://github.com/kubernetes-sigs/cri-tools - (cd cri-tools && git checkout tags/v1.11.0 && make && sudo -n -E make install) + # Install containerd & cri-tools. + GOPATH=$(mktemp -d --tmpdir gopathXXXXX) + install_helper github.com/containerd/containerd v1.2.2 "${GOPATH}" + install_helper github.com/kubernetes-sigs/cri-tools v1.11.0 "${GOPATH}" # Install gvisor-containerd-shim. local latest=/tmp/gvisor-containerd-shim-latest @@ -143,7 +156,8 @@ EOF sudo mv ${shim_config_tmp_path} ${shim_config_path} # Configure CNI. - sudo -n -E env PATH=${PATH} containerd/script/setup/install-cni + (cd "${GOPATH}" && sudo -n -E env PATH="${PATH}" GOPATH="${GOPATH}" \ + src/github.com/containerd/containerd/script/setup/install-cni) } # Run the tests that require docker. -- cgit v1.2.3 From 69c8657a66ac1a7e3bfd388de0a7cd28ac4b51cd Mon Sep 17 00:00:00 2001 From: Andrei Vagin Date: Tue, 11 Jun 2019 16:35:42 -0700 Subject: kokoro: don't overwrite test results for different runtimes PiperOrigin-RevId: 252724255 --- tools/run_tests.sh | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'tools') diff --git a/tools/run_tests.sh b/tools/run_tests.sh index b35d2e4b8..8874794fd 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -175,13 +175,17 @@ run_docker_tests() { # configuration, e.g. save/restore not supported with hostnet. declare -a variations=("" "-kvm" "-hostnet" "-overlay") for v in "${variations[@]}"; do + # Change test names otherwise each run of tests will overwrite logs and + # results of the previous run. + sed -i "s/name = \"integration_test.*\"/name = \"integration_test${v}\"/" runsc/test/integration/BUILD + sed -i "s/name = \"image_test.*\"/name = \"image_test${v}\"/" runsc/test/image/BUILD # Run runsc tests with docker that are tagged manual. bazel test \ "${BAZEL_BUILD_FLAGS[@]}" \ --test_env=RUNSC_RUNTIME="${RUNTIME}${v}" \ --test_output=all \ - //runsc/test/image:image_test \ - //runsc/test/integration:integration_test + //runsc/test/image:image_test${v} \ + //runsc/test/integration:integration_test${v} done } -- cgit v1.2.3 From 356d1be140bb51f2a50d2c7fe24242cbfeedc9d6 Mon Sep 17 00:00:00 2001 From: Fabricio Voznika Date: Wed, 12 Jun 2019 09:40:50 -0700 Subject: Allow 'runsc do' to run without root '--rootless' flag lets a non-root user execute 'runsc do'. The drawback is that the sandbox and gofer processes will run as root inside a user namespace that is mapped to the caller's user, intead of nobody. And network is defaulted to '--network=host' inside the root network namespace. On the bright side, it's very convenient for testing: runsc --rootless do ls runsc --rootless do curl www.google.com PiperOrigin-RevId: 252840970 --- runsc/boot/config.go | 7 +++ runsc/cmd/boot.go | 22 +++++----- runsc/cmd/capability_test.go | 2 +- runsc/cmd/create.go | 8 +++- runsc/cmd/do.go | 39 ++++++++++++----- runsc/cmd/restore.go | 10 +++-- runsc/cmd/run.go | 8 +++- runsc/container/container_test.go | 3 +- runsc/main.go | 63 +++++++++++++++------------ runsc/sandbox/sandbox.go | 92 +++++++++++++++++++++++---------------- runsc/specutils/BUILD | 5 +-- runsc/specutils/namespace.go | 52 ++++++++++++++++++++++ runsc/test/testutil/BUILD | 1 - runsc/test/testutil/testutil.go | 50 --------------------- tools/run_tests.sh | 4 +- 15 files changed, 212 insertions(+), 154 deletions(-) (limited to 'tools') diff --git a/runsc/boot/config.go b/runsc/boot/config.go index 8564c502d..6112b6c0a 100644 --- a/runsc/boot/config.go +++ b/runsc/boot/config.go @@ -226,6 +226,12 @@ type Config struct { // to the same underlying network device. This allows netstack to better // scale for high throughput use cases. NumNetworkChannels int + + // Rootless allows the sandbox to be started with a user that is not root. + // Defense is depth measures are weaker with rootless. Specifically, the + // sandbox and Gofer process run as root inside a user namespace with root + // mapped to the caller's user. + Rootless bool } // ToFlags returns a slice of flags that correspond to the given Config. @@ -250,6 +256,7 @@ func (c *Config) ToFlags() []string { "--profile=" + strconv.FormatBool(c.ProfileEnable), "--net-raw=" + strconv.FormatBool(c.EnableRaw), "--num-network-channels=" + strconv.Itoa(c.NumNetworkChannels), + "--rootless=" + strconv.FormatBool(c.Rootless), } if c.TestOnlyAllowRunAsCurrentUserWithoutChroot { // Only include if set since it is never to be used by users. diff --git a/runsc/cmd/boot.go b/runsc/cmd/boot.go index 3a547d4aa..e0a950e9c 100644 --- a/runsc/cmd/boot.go +++ b/runsc/cmd/boot.go @@ -130,6 +130,8 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) // Ensure that if there is a panic, all goroutine stacks are printed. debug.SetTraceback("all") + conf := args[0].(*boot.Config) + if b.setUpRoot { if err := setUpChroot(b.pidns); err != nil { Fatalf("error setting up chroot: %v", err) @@ -143,14 +145,16 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) args = append(args, arg) } } - // Note that we've already read the spec from the spec FD, and - // we will read it again after the exec call. This works - // because the ReadSpecFromFile function seeks to the beginning - // of the file before reading. - if err := callSelfAsNobody(args); err != nil { - Fatalf("%v", err) + if !conf.Rootless { + // Note that we've already read the spec from the spec FD, and + // we will read it again after the exec call. This works + // because the ReadSpecFromFile function seeks to the beginning + // of the file before reading. + if err := callSelfAsNobody(args); err != nil { + Fatalf("%v", err) + } + panic("callSelfAsNobody must never return success") } - panic("callSelfAsNobody must never return success") } } @@ -163,9 +167,6 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) } specutils.LogSpec(spec) - conf := args[0].(*boot.Config) - waitStatus := args[1].(*syscall.WaitStatus) - if b.applyCaps { caps := spec.Process.Capabilities if caps == nil { @@ -251,6 +252,7 @@ func (b *Boot) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) ws := l.WaitExit() log.Infof("application exiting with %+v", ws) + waitStatus := args[1].(*syscall.WaitStatus) *waitStatus = syscall.WaitStatus(ws.Status()) l.Destroy() return subcommands.ExitSuccess diff --git a/runsc/cmd/capability_test.go b/runsc/cmd/capability_test.go index ee74d33d8..2825dfaa5 100644 --- a/runsc/cmd/capability_test.go +++ b/runsc/cmd/capability_test.go @@ -116,6 +116,6 @@ func TestCapabilities(t *testing.T) { } func TestMain(m *testing.M) { - testutil.RunAsRoot() + specutils.MaybeRunAsRoot() os.Exit(m.Run()) } diff --git a/runsc/cmd/create.go b/runsc/cmd/create.go index 8bf9b7dcf..e82e8c667 100644 --- a/runsc/cmd/create.go +++ b/runsc/cmd/create.go @@ -82,13 +82,17 @@ func (c *Create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} id := f.Arg(0) conf := args[0].(*boot.Config) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", c.Name()) + } + bundleDir := c.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) @@ -96,7 +100,7 @@ func (c *Create) Execute(_ context.Context, f *flag.FlagSet, args ...interface{} // container unless the metadata specifies that it should be run in an // existing container. if _, err := container.Create(id, spec, conf, bundleDir, c.consoleSocket, c.pidFile, c.userLog); err != nil { - Fatalf("creating container: %v", err) + return Errorf("creating container: %v", err) } return subcommands.ExitSuccess } diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index 8ea59046c..3f6e46fce 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -39,10 +39,9 @@ import ( // Do implements subcommands.Command for the "do" command. It sets up a simple // sandbox and executes the command inside it. See Usage() for more details. type Do struct { - root string - cwd string - ip string - networkNamespace bool + root string + cwd string + ip string } // Name implements subcommands.Command.Name. @@ -72,7 +71,6 @@ func (c *Do) SetFlags(f *flag.FlagSet) { f.StringVar(&c.root, "root", "/", `path to the root directory, defaults to "/"`) f.StringVar(&c.cwd, "cwd", ".", "path to the current directory, defaults to the current directory") f.StringVar(&c.ip, "ip", "192.168.10.2", "IPv4 address for the sandbox") - f.BoolVar(&c.networkNamespace, "netns", true, "run in a new network namespace") } // Execute implements subcommands.Command.Execute. @@ -85,15 +83,21 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) - // Map the entire host file system, but make it readonly with a writable - // overlay on top (ignore --overlay option). - conf.Overlay = true + if conf.Rootless { + if err := specutils.MaybeRunAsRoot(); err != nil { + return Errorf("Error executing inside namespace: %v", err) + } + // Execution will continue here if no more capabilities are needed... + } hostname, err := os.Hostname() if err != nil { return Errorf("Error to retrieve hostname: %v", err) } + // Map the entire host file system, but make it readonly with a writable + // overlay on top (ignore --overlay option). + conf.Overlay = true absRoot, err := resolvePath(c.root) if err != nil { return Errorf("Error resolving root: %v", err) @@ -119,11 +123,22 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) su specutils.LogSpec(spec) cid := fmt.Sprintf("runsc-%06d", rand.Int31n(1000000)) - if !c.networkNamespace { - if conf.Network != boot.NetworkHost { - Fatalf("The current network namespace can be used only if --network=host is set", nil) + if conf.Network == boot.NetworkNone { + netns := specs.LinuxNamespace{ + Type: specs.NetworkNamespace, + } + if spec.Linux != nil { + panic("spec.Linux is not nil") } - } else if conf.Network != boot.NetworkNone { + spec.Linux = &specs.Linux{Namespaces: []specs.LinuxNamespace{netns}} + + } else if conf.Rootless { + if conf.Network == boot.NetworkSandbox { + fmt.Println("*** Rootless requires changing network type to host ***") + conf.Network = boot.NetworkHost + } + + } else { clean, err := c.setupNet(cid, spec) if err != nil { return Errorf("Error setting up network: %v", err) diff --git a/runsc/cmd/restore.go b/runsc/cmd/restore.go index 3ab2f5676..a78a0dce6 100644 --- a/runsc/cmd/restore.go +++ b/runsc/cmd/restore.go @@ -80,25 +80,29 @@ func (r *Restore) Execute(_ context.Context, f *flag.FlagSet, args ...interface{ conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", r.Name()) + } + bundleDir := r.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) if r.imagePath == "" { - Fatalf("image-path flag must be provided") + return Errorf("image-path flag must be provided") } conf.RestoreFile = filepath.Join(r.imagePath, checkpointFileName) ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile, r.userLog, r.detach) if err != nil { - Fatalf("running container: %v", err) + return Errorf("running container: %v", err) } *waitStatus = ws diff --git a/runsc/cmd/run.go b/runsc/cmd/run.go index c228b4f93..abf602239 100644 --- a/runsc/cmd/run.go +++ b/runsc/cmd/run.go @@ -67,19 +67,23 @@ func (r *Run) Execute(_ context.Context, f *flag.FlagSet, args ...interface{}) s conf := args[0].(*boot.Config) waitStatus := args[1].(*syscall.WaitStatus) + if conf.Rootless { + return Errorf("Rootless mode not supported with %q", r.Name()) + } + bundleDir := r.bundleDir if bundleDir == "" { bundleDir = getwdOrDie() } spec, err := specutils.ReadSpec(bundleDir) if err != nil { - Fatalf("reading spec: %v", err) + return Errorf("reading spec: %v", err) } specutils.LogSpec(spec) ws, err := container.Run(id, spec, conf, bundleDir, r.consoleSocket, r.pidFile, r.userLog, r.detach) if err != nil { - Fatalf("running container: %v", err) + return Errorf("running container: %v", err) } *waitStatus = ws diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index 72c5ecbb0..867bf8187 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -36,6 +36,7 @@ import ( "gvisor.googlesource.com/gvisor/pkg/sentry/control" "gvisor.googlesource.com/gvisor/pkg/sentry/kernel/auth" "gvisor.googlesource.com/gvisor/runsc/boot" + "gvisor.googlesource.com/gvisor/runsc/specutils" "gvisor.googlesource.com/gvisor/runsc/test/testutil" ) @@ -1853,7 +1854,7 @@ func TestMain(m *testing.M) { if err := testutil.ConfigureExePath(); err != nil { panic(err.Error()) } - testutil.RunAsRoot() + specutils.MaybeRunAsRoot() os.Exit(m.Run()) } diff --git a/runsc/main.go b/runsc/main.go index a214f6ba0..cfe3a78d0 100644 --- a/runsc/main.go +++ b/runsc/main.go @@ -61,16 +61,19 @@ var ( straceLogSize = flag.Uint("strace-log-size", 1024, "default size (in bytes) to log data argument blobs") // Flags that control sandbox runtime behavior. - platform = flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm") - network = flag.String("network", "sandbox", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.") - gso = flag.Bool("gso", true, "enable generic segmenation offload") - fileAccess = flag.String("file-access", "exclusive", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.") - overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.") - watchdogAction = flag.String("watchdog-action", "log", "sets what action the watchdog takes when triggered: log (default), panic.") - panicSignal = flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.") - profile = flag.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).") - netRaw = flag.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.") - numNetworkChannels = flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") + platform = flag.String("platform", "ptrace", "specifies which platform to use: ptrace (default), kvm") + network = flag.String("network", "sandbox", "specifies which network to use: sandbox (default), host, none. Using network inside the sandbox is more secure because it's isolated from the host network.") + gso = flag.Bool("gso", true, "enable generic segmenation offload") + fileAccess = flag.String("file-access", "exclusive", "specifies which filesystem to use for the root mount: exclusive (default), shared. Volume mounts are always shared.") + overlay = flag.Bool("overlay", false, "wrap filesystem mounts with writable overlay. All modifications are stored in memory inside the sandbox.") + watchdogAction = flag.String("watchdog-action", "log", "sets what action the watchdog takes when triggered: log (default), panic.") + panicSignal = flag.Int("panic-signal", -1, "register signal handling that panics. Usually set to SIGUSR2(12) to troubleshoot hangs. -1 disables it.") + profile = flag.Bool("profile", false, "prepares the sandbox to use Golang profiler. Note that enabling profiler loosens the seccomp protection added to the sandbox (DO NOT USE IN PRODUCTION).") + netRaw = flag.Bool("net-raw", false, "enable raw sockets. When false, raw sockets are disabled by removing CAP_NET_RAW from containers (`runsc exec` will still be able to utilize raw sockets). Raw sockets allow malicious containers to craft packets and potentially attack the network.") + numNetworkChannels = flag.Int("num-network-channels", 1, "number of underlying channels(FDs) to use for network link endpoints.") + rootless = flag.Bool("rootless", false, "it allows the sandbox to be started with a user that is not root. Sandbox and Gofer processes may run with same privileges as current user.") + + // Test flags, not to be used outside tests, ever. testOnlyAllowRunAsCurrentUserWithoutChroot = flag.Bool("TESTONLY-unsafe-nonroot", false, "TEST ONLY; do not ever use! This skips many security measures that isolate the host from the sandbox.") ) @@ -166,26 +169,28 @@ func main() { // Create a new Config from the flags. conf := &boot.Config{ - RootDir: *rootDir, - Debug: *debug, - LogFilename: *logFilename, - LogFormat: *logFormat, - DebugLog: *debugLog, - DebugLogFormat: *debugLogFormat, - FileAccess: fsAccess, - Overlay: *overlay, - Network: netType, - GSO: *gso, - LogPackets: *logPackets, - Platform: platformType, - Strace: *strace, - StraceLogSize: *straceLogSize, - WatchdogAction: wa, - PanicSignal: *panicSignal, - ProfileEnable: *profile, - EnableRaw: *netRaw, + RootDir: *rootDir, + Debug: *debug, + LogFilename: *logFilename, + LogFormat: *logFormat, + DebugLog: *debugLog, + DebugLogFormat: *debugLogFormat, + FileAccess: fsAccess, + Overlay: *overlay, + Network: netType, + GSO: *gso, + LogPackets: *logPackets, + Platform: platformType, + Strace: *strace, + StraceLogSize: *straceLogSize, + WatchdogAction: wa, + PanicSignal: *panicSignal, + ProfileEnable: *profile, + EnableRaw: *netRaw, + NumNetworkChannels: *numNetworkChannels, + Rootless: *rootless, + TestOnlyAllowRunAsCurrentUserWithoutChroot: *testOnlyAllowRunAsCurrentUserWithoutChroot, - NumNetworkChannels: *numNetworkChannels, } if len(*straceSyscalls) != 0 { conf.StraceSyscalls = strings.Split(*straceSyscalls, ",") diff --git a/runsc/sandbox/sandbox.go b/runsc/sandbox/sandbox.go index 032190636..5ff6f879c 100644 --- a/runsc/sandbox/sandbox.go +++ b/runsc/sandbox/sandbox.go @@ -515,46 +515,64 @@ func (s *Sandbox) createSandboxProcess(spec *specs.Spec, conf *boot.Config, bund } else if specutils.HasCapabilities(capability.CAP_SETUID, capability.CAP_SETGID) { log.Infof("Sandbox will be started in new user namespace") nss = append(nss, specs.LinuxNamespace{Type: specs.UserNamespace}) + cmd.Args = append(cmd.Args, "--setup-root") - // Map nobody in the new namespace to nobody in the parent namespace. - // - // A sandbox process will construct an empty - // root for itself, so it has to have the CAP_SYS_ADMIN - // capability. - // - // FIXME(b/122554829): The current implementations of - // os/exec doesn't allow to set ambient capabilities if - // a process is started in a new user namespace. As a - // workaround, we start the sandbox process with the 0 - // UID and then it constructs a chroot and sets UID to - // nobody. https://github.com/golang/go/issues/2315 - const nobody = 65534 - cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ - { - ContainerID: int(0), - HostID: int(nobody - 1), - Size: int(1), - }, - { - ContainerID: int(nobody), - HostID: int(nobody), - Size: int(1), - }, - } - cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{ - { - ContainerID: int(nobody), - HostID: int(nobody), - Size: int(1), - }, + if conf.Rootless { + log.Infof("Rootless mode: sandbox will run as root inside user namespace, mapped to the current user, uid: %d, gid: %d", os.Getuid(), os.Getgid()) + cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: os.Getuid(), + Size: 1, + }, + } + cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: os.Getgid(), + Size: 1, + }, + } + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 0, Gid: 0} + + } else { + // Map nobody in the new namespace to nobody in the parent namespace. + // + // A sandbox process will construct an empty + // root for itself, so it has to have the CAP_SYS_ADMIN + // capability. + // + // FIXME(b/122554829): The current implementations of + // os/exec doesn't allow to set ambient capabilities if + // a process is started in a new user namespace. As a + // workaround, we start the sandbox process with the 0 + // UID and then it constructs a chroot and sets UID to + // nobody. https://github.com/golang/go/issues/2315 + const nobody = 65534 + cmd.SysProcAttr.UidMappings = []syscall.SysProcIDMap{ + { + ContainerID: 0, + HostID: nobody - 1, + Size: 1, + }, + { + ContainerID: nobody, + HostID: nobody, + Size: 1, + }, + } + cmd.SysProcAttr.GidMappings = []syscall.SysProcIDMap{ + { + ContainerID: nobody, + HostID: nobody, + Size: 1, + }, + } + + // Set credentials to run as user and group nobody. + cmd.SysProcAttr.Credential = &syscall.Credential{Uid: 0, Gid: nobody} } - // Set credentials to run as user and group nobody. - cmd.SysProcAttr.Credential = &syscall.Credential{ - Uid: 0, - Gid: nobody, - } - cmd.Args = append(cmd.Args, "--setup-root") } else { return fmt.Errorf("can't run sandbox process as user nobody since we don't have CAP_SETUID or CAP_SETGID") } diff --git a/runsc/specutils/BUILD b/runsc/specutils/BUILD index 15476de6f..0456e4c4f 100644 --- a/runsc/specutils/BUILD +++ b/runsc/specutils/BUILD @@ -10,10 +10,7 @@ go_library( "specutils.go", ], importpath = "gvisor.googlesource.com/gvisor/runsc/specutils", - visibility = [ - "//runsc:__subpackages__", - "//test:__subpackages__", - ], + visibility = ["//:sandbox"], deps = [ "//pkg/abi/linux", "//pkg/log", diff --git a/runsc/specutils/namespace.go b/runsc/specutils/namespace.go index 7d194335c..06c13d1ab 100644 --- a/runsc/specutils/namespace.go +++ b/runsc/specutils/namespace.go @@ -220,3 +220,55 @@ func HasCapabilities(cs ...capability.Cap) bool { } return true } + +// MaybeRunAsRoot ensures the process runs with capabilities needed to create a +// sandbox, e.g. CAP_SYS_ADMIN, CAP_SYS_CHROOT, etc. If capabilities are needed, +// it will create a new user namespace and re-execute the process as root +// inside the namespace with the same arguments and environment. +// +// This function returns immediately when no new capability is needed. If +// another process is executed, it returns straight from here with the same exit +// code as the child. +func MaybeRunAsRoot() error { + if HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_SYS_CHROOT, capability.CAP_SETUID, capability.CAP_SETGID) { + return nil + } + + // Current process doesn't have required capabilities, create user namespace + // and run as root inside the namespace to acquire capabilities. + log.Infof("*** Re-running as root in new user namespace ***") + + cmd := exec.Command("/proc/self/exe", os.Args[1:]...) + + cmd.SysProcAttr = &syscall.SysProcAttr{ + Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS, + // Set current user/group as root inside the namespace. Since we may not + // have CAP_SETUID/CAP_SETGID, just map root to the current user/group. + UidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getuid(), Size: 1}, + }, + GidMappings: []syscall.SysProcIDMap{ + {ContainerID: 0, HostID: os.Getgid(), Size: 1}, + }, + Credential: &syscall.Credential{Uid: 0, Gid: 0}, + GidMappingsEnableSetgroups: false, + } + + cmd.Env = os.Environ() + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + if exit, ok := err.(*exec.ExitError); ok { + if ws, ok := exit.Sys().(syscall.WaitStatus); ok { + os.Exit(ws.ExitStatus()) + } + log.Warningf("No wait status provided, exiting with -1: %v", err) + os.Exit(-1) + } + return fmt.Errorf("re-executing self: %v", err) + } + // Child completed with success. + os.Exit(0) + panic("unreachable") +} diff --git a/runsc/test/testutil/BUILD b/runsc/test/testutil/BUILD index ddec81444..eedf962a4 100644 --- a/runsc/test/testutil/BUILD +++ b/runsc/test/testutil/BUILD @@ -18,6 +18,5 @@ go_library( "@com_github_cenkalti_backoff//:go_default_library", "@com_github_kr_pty//:go_default_library", "@com_github_opencontainers_runtime-spec//specs-go:go_default_library", - "@com_github_syndtr_gocapability//capability:go_default_library", ], ) diff --git a/runsc/test/testutil/testutil.go b/runsc/test/testutil/testutil.go index 727b648a6..1bd5adc54 100644 --- a/runsc/test/testutil/testutil.go +++ b/runsc/test/testutil/testutil.go @@ -30,7 +30,6 @@ import ( "os/exec" "os/signal" "path/filepath" - "runtime" "strings" "sync" "sync/atomic" @@ -39,7 +38,6 @@ import ( "github.com/cenkalti/backoff" specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/syndtr/gocapability/capability" "gvisor.googlesource.com/gvisor/runsc/boot" "gvisor.googlesource.com/gvisor/runsc/specutils" ) @@ -284,54 +282,6 @@ func WaitForHTTP(port int, timeout time.Duration) error { return Poll(cb, timeout) } -// RunAsRoot ensures the test runs with CAP_SYS_ADMIN and CAP_SYS_CHROOT. If -// needed it will create a new user namespace and re-execute the test as root -// inside of the namespace. This function returns when it's running as root. If -// it needs to create another process, it will exit from there and not return. -func RunAsRoot() { - if specutils.HasCapabilities(capability.CAP_SYS_ADMIN, capability.CAP_SYS_CHROOT) { - return - } - - fmt.Println("*** Re-running test as root in new user namespace ***") - - // Current process doesn't have CAP_SYS_ADMIN, create user namespace and run - // as root inside that namespace to get it. - runtime.LockOSThread() - defer runtime.UnlockOSThread() - - cmd := exec.Command("/proc/self/exe", os.Args[1:]...) - cmd.SysProcAttr = &syscall.SysProcAttr{ - Cloneflags: syscall.CLONE_NEWUSER | syscall.CLONE_NEWNS, - // Set current user/group as root inside the namespace. - UidMappings: []syscall.SysProcIDMap{ - {ContainerID: 0, HostID: os.Getuid(), Size: 1}, - }, - GidMappings: []syscall.SysProcIDMap{ - {ContainerID: 0, HostID: os.Getgid(), Size: 1}, - }, - GidMappingsEnableSetgroups: false, - Credential: &syscall.Credential{ - Uid: 0, - Gid: 0, - }, - } - cmd.Env = os.Environ() - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - if err := cmd.Run(); err != nil { - if exit, ok := err.(*exec.ExitError); ok { - if ws, ok := exit.Sys().(syscall.WaitStatus); ok { - os.Exit(ws.ExitStatus()) - } - os.Exit(-1) - } - panic(fmt.Sprint("error running child process:", err.Error())) - } - os.Exit(0) -} - // Reaper reaps child processes. type Reaper struct { // mu protects ch, which will be nil if the reaper is not running. diff --git a/tools/run_tests.sh b/tools/run_tests.sh index 8874794fd..7a1f889dd 100755 --- a/tools/run_tests.sh +++ b/tools/run_tests.sh @@ -212,8 +212,8 @@ run_runsc_do_tests() { local runsc=$(find bazel-bin/runsc -type f -executable -name "runsc" | head -n1) # run runsc do without root privileges. - unshare -Ur ${runsc} --network=none --TESTONLY-unsafe-nonroot do true - unshare -Ur ${runsc} --TESTONLY-unsafe-nonroot --network=host do --netns=false true + ${runsc} --rootless do true + ${runsc} --rootless --network=none do true # run runsc do with root privileges. sudo -n -E ${runsc} do true -- cgit v1.2.3