Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tests for command-line interface #218

Merged
merged 19 commits into from
May 9, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ bin/staticcheck
bin/gocovmerge
bin/misspell
bin/config
cli-tests/*.out.actual
*coverage.out
.vscode
tags
2 changes: 2 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ jobs:

- stage: presubmits
name: Generate, Format, and Lint
before_install:
- sudo apt-get -y install shellcheck
install:
- make tools
script:
Expand Down
26 changes: 20 additions & 6 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,11 @@ On every pull request, [Travis CI](https://travis-ci.org/google/fscrypt) runs
unit tests, integration tests, code formatters, and linters. To pass these
checks you should make sure that in your submission:
- `make` properly builds `fscrypt` and `pam_fscrypt.so`.
- All tests, including [integration tests](#running-integration-tests), should pass.
- All tests, including [integration tests](#running-integration-tests) and
[command-line interface (CLI)
tests](https://github.com/google/fscrypt/blob/master/cli-tests/README.md),
should pass. If the CLI tests fail due to an expected change in output, you
can use `make cli-test-update`.
- `make format` has been run.
- If you made any changes to files ending in `.proto`, the corresponding
`.pb.go` files should be regenerated with `make gen`.
Expand All @@ -74,17 +78,27 @@ Essentially, if you run:
make test-setup
make all
make test-teardown
make cli-test
go mod tidy
```
and everything succeeds, and no files are changed, you're good to submit.

The `Makefile` should automatically download and build whatever it needs.
The only exceptions to this rule are:
The `Makefile` will automatically download and build any needed Go dependencies.
However, you'll also need to install some non-Go dependencies:
- `make format` requires
[`clang-format`](https://clang.llvm.org/docs/ClangFormat.html).
- `make test-setup` requires
[`e2fsprogs`](https://en.wikipedia.org/wiki/E2fsprogs) version 1.43
or later (or any patched version that supports `-O encrypt`).
- `make lint` requires [`shellcheck`](https://github.com/koalaman/shellcheck).
- `make test-setup` and `make cli-test` require
[`e2fsprogs`](https://en.wikipedia.org/wiki/E2fsprogs) version 1.43 or
later.
- `make cli-test` requires [`expect`](https://en.wikipedia.org/wiki/Expect)
and
[`keyutils`](https://manpages.debian.org/testing/keyutils/keyctl.1.en.html).

On Ubuntu, the following command installs the needed packages:
```
sudo apt-get install clang-format shellcheck e2fsprogs expect keyutils
```

### Running Integration Tests

Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -110,11 +110,12 @@ lint: $(BIN)/golint $(BIN)/staticcheck $(BIN)/misspell
go list ./... | xargs -L1 golint -set_exit_status
staticcheck ./...
misspell -source=text $(FILES)
( cd cli-tests && shellcheck -x *.sh)

clean:
rm -f $(BIN)/$(NAME) $(PAM_MODULE) $(TOOLS) coverage.out $(COVERAGE_FILES) $(PAM_CONFIG)

###### Testing Commands (setup/teardown require sudo) ######
###### Go tests ######
.PHONY: test test-setup test-teardown

# If MOUNT exists signal that we should run integration tests.
Expand All @@ -139,6 +140,15 @@ test-teardown:
rmdir $(MOUNT)
rm -f $(IMAGE)

###### Command-line interface tests ######
.PHONY: cli-test cli-test-update

cli-test: $(BIN)/$(NAME)
sudo cli-tests/run.sh

cli-test-update: $(BIN)/$(NAME)
sudo cli-tests/run.sh --update-output

# Runs tests and generates coverage
COVERAGE_FILES := $(addsuffix coverage.out,$(GO_DIRS))
coverage.out: $(BIN)/gocovmerge $(COVERAGE_FILES)
Expand Down
4 changes: 4 additions & 0 deletions actions/protector.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ import (
"github.com/google/fscrypt/util"
)

// LoginProtectorMountpoint is the mountpoint where login protectors are stored.
// This can be overridden by the user of this package.
var LoginProtectorMountpoint = "/"

// Errors relating to Protectors
var (
ErrProtectorName = errors.New("login protectors do not need a name")
Expand Down
67 changes: 67 additions & 0 deletions cli-tests/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# fscrypt command-line interface tests

## Usage

To run the command-line interface (CLI) tests for `fscrypt`, ensure
that your kernel is v5.4 or later and has `CONFIG_FS_ENCRYPTION=y`.
Also ensure that you have the following packages installed:

* e2fsprogs
* expect
* keyutils

Then, run:

```shell
make cli-test
```

You'll need to enter your `sudo` password, as the tests require root.

If you only want to run specific tests, run a command like:

```shell
make && sudo cli-tests/run.sh t_encrypt t_unlock
```

## Updating the expected output

When the output of `fscrypt` has intentionally changed, the test
`.out` files need to be updated. This can be done automatically by
the following command, but be sure to review the changes:

```shell
make cli-test-update
```

## Writing CLI tests

The fscrypt CLI tests are `bash` scripts named like `t_*.sh`.

The test scripts must be executable and begin by sourcing `common.sh`.
They all run in bash "extra-strict mode" (`-e -u -o pipefail`). They
run as root and have access to the following environment:

* `$DEV`, `$DEV_ROOT`: ext4 filesystem images with encryption enabled

* `$MNT`, `$MNT_ROOT`: the mountpoints of the above filesystems.
Initially all filesystems are mounted and are setup for fscrypt.
Login protectors will be stored on `$MNT_ROOT`.

* `$TMPDIR`: a temporary directory that the test may use

* `$FSCRYPT_CONF`: location of the fscrypt.conf file. Initially this
file exists and specifies to use v2 policies with the default
settings, except password hashing is configured to be extra fast.

* `$TEST_USER`: a non-root user that the test may use. Their password
is `TEST_USER_PASS`.

Any output (stdout and stderr) the test prints is compared to the
corresponding `.out` file. If a difference is detected then the test
is considered to have failed. The output is first sent through some
standard filters; see `run.sh`.

The test is also failed if it exits with nonzero status.

See `common.sh` for utility functions the tests may use.
154 changes: 154 additions & 0 deletions cli-tests/common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
#!/bin/bash
#
# common.sh - helper functions for fscrypt command-line interface tests
#
# Copyright 2020 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
#
# 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.
#

# Use extra-strict mode.
set -e -u -o pipefail

# Don't allow running the test scripts directly. They need to be run via
# run.sh, to set up everything correctly.
if [ -z "${MNT:-}" ] || [ -z "${MNT_ROOT:-}" ]; then
echo 1>&2 "ERROR: This script can only be run via run.sh, not on its own."
exit 1
fi

# Prints an error message, then fails the test by exiting with failure status.
_fail()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"
echo 1>&2 "ERROR: $1"
exit 1
}

# Runs a shell command and expects that it fails.
_expect_failure()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"
if eval "$1"; then
_fail "command unexpectedly succeeded: \"$1\""
fi
}

# Prints a message to mark the beginning of the next part of the test.
_print_header()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"
echo
echo "# $1"
}

# Deletes all files on the test filesystems, including all policies and
# protectors. Leaves the fscrypt metadata directories themselves.
_reset_filesystems()
{
local mnt

[ $# -ne 0 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

for mnt in "$MNT" "$MNT_ROOT"; do
rm -rf "${mnt:?}"/* "${mnt:?}"/.fscrypt/{policies,protectors}/*
done
}

# Prints the number of filesystems that have encryption support enabled.
_get_enabled_fs_count()
{
local count

[ $# -ne 0 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

count=$(fscrypt status | awk '/filesystems supporting encryption/ { print $4 }')
if [ -z "$count" ]; then
_fail "encryption support status line not found"
fi
echo "$count"
}

# Prints the number of filesystems that have fscrypt metadata.
_get_setup_fs_count()
{
local count

[ $# -ne 0 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

count=$(fscrypt status | awk '/filesystems with fscrypt metadata/ { print $5 }')
if [ -z "$count" ]; then
_fail "fscrypt metadata status line not found"
fi
echo "$count"
}

# Removes all fscrypt metadata from the given filesystem.
_rm_metadata()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

rm -r "${1:?}/.fscrypt"
}

# Runs a shell command, ignoring its output (stdout and stderr) if it succeeds.
# If the command fails, prints its output and fails the test.
_run_noisy_command()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

if ! eval "$1" &> "$TMPDIR/out"; then
_fail "Command failed: '$1'. Output was: $(cat "$TMPDIR/out")"
fi
}

# Runs the given shell command as the test user.
_user_do()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

su "$TEST_USER" --command="$1"
}

# Runs the given shell command as the test user and expects it to fail.
_user_do_and_expect_failure()
{
[ $# -ne 1 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

_expect_failure "_user_do '$1'"
}

# Gives the test a new session keyring which contains the test user's keyring
# but not root's keyring. Also clears the test user's keyring. This must be
# called at the beginning of the test script as it may re-execute the script.
_setup_session_keyring()
{
[ $# -ne 0 ] && _fail "wrong argument count to ${FUNCNAME[0]}"

# This *should* just use 'keyctl new_session', but that doesn't work if
# the session keyring is owned by a user other than root. So instead we
# have to use 'keyctl session' and re-execute the script.
if [ -z "${FSCRYPT_SESSION_KEYRING_SET:-}" ]; then
export FSCRYPT_SESSION_KEYRING_SET=1
set +e
keyctl session - "$0" |& grep -v '^Joined session keyring'
exit "${PIPESTATUS[0]}"
fi

# Link the test user's keyring into the new session keyring.
keyctl setperm @s 0x3f000000 # all possessor permissions
_user_do "keyctl link @u @s"

# Clear the test user's keyring.
_user_do "keyctl clear @u"
}
Loading