| // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "cros-disks/sshfs_helper.h" |
| |
| #include <sys/stat.h> |
| |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/check.h> |
| #include <base/check_op.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <base/strings/string_split.h> |
| #include <base/strings/string_util.h> |
| #include <base/strings/stringprintf.h> |
| #include <brillo/process/process_reaper.h> |
| #include <gmock/gmock.h> |
| #include <gtest/gtest.h> |
| |
| #include "cros-disks/fuse_mounter.h" |
| #include "cros-disks/mount_options.h" |
| #include "cros-disks/platform.h" |
| #include "cros-disks/uri.h" |
| |
| using testing::_; |
| using testing::AllOf; |
| using testing::EndsWith; |
| using testing::HasSubstr; |
| using testing::IsSupersetOf; |
| using testing::Not; |
| using testing::Return; |
| using testing::StartsWith; |
| using testing::StrEq; |
| using testing::UnorderedElementsAre; |
| |
| namespace cros_disks { |
| |
| namespace { |
| |
| const uid_t kMountUID = 200; |
| const gid_t kMountGID = 201; |
| const base::FilePath kWorkingDir("/wkdir"); |
| const base::FilePath kMountDir("/mnt"); |
| const Uri kSomeSource("sshfs", "src"); |
| |
| std::vector<std::string> ParseOptions(const SandboxedProcess& sandbox) { |
| CHECK_EQ(3, sandbox.arguments().size()); |
| CHECK_EQ("src", sandbox.arguments()[0]); |
| CHECK_EQ("-o", sandbox.arguments()[1]); |
| return base::SplitString(sandbox.arguments()[2], ",", |
| base::WhitespaceHandling::KEEP_WHITESPACE, |
| base::SplitResult::SPLIT_WANT_ALL); |
| } |
| |
| // Mock Platform implementation for testing. |
| class MockPlatform : public Platform { |
| public: |
| MockPlatform() = default; |
| |
| bool GetUserAndGroupId(const std::string& user, |
| uid_t* user_id, |
| gid_t* group_id) const override { |
| if (user == "fuse-sshfs") { |
| if (user_id) |
| *user_id = kMountUID; |
| if (group_id) |
| *group_id = kMountGID; |
| return true; |
| } |
| return false; |
| } |
| |
| MOCK_METHOD(bool, |
| SetOwnership, |
| (const std::string&, uid_t, gid_t), |
| (const, override)); |
| }; |
| |
| } // namespace |
| |
| class SshfsHelperTest : public ::testing::Test { |
| public: |
| SshfsHelperTest() { |
| ON_CALL(platform_, SetOwnership(_, kMountUID, getgid())) |
| .WillByDefault(Return(true)); |
| ON_CALL(platform_, SetOwnership(_, kMountUID, kMountGID)) |
| .WillByDefault(Return(true)); |
| } |
| |
| void SetUp() override { |
| CHECK(working_dir_.CreateUniqueTempDir()); |
| helper_ = std::make_unique<SshfsHelper>(&platform_, &process_reaper_, |
| working_dir_.GetPath()); |
| } |
| |
| protected: |
| MountErrorType ConfigureSandbox(const std::string& source, |
| std::vector<std::string> params, |
| std::vector<std::string>* args) { |
| FakeSandboxedProcess sandbox; |
| MountErrorType error = helper_->ConfigureSandbox( |
| source, kMountDir, std::move(params), &sandbox); |
| if (error == MOUNT_ERROR_NONE) { |
| *args = ParseOptions(sandbox); |
| } |
| return error; |
| } |
| |
| MockPlatform platform_; |
| brillo::ProcessReaper process_reaper_; |
| base::ScopedTempDir working_dir_; |
| std::unique_ptr<SshfsHelper> helper_; |
| }; |
| |
| TEST_F(SshfsHelperTest, ConfigureSandbox) { |
| EXPECT_CALL(platform_, SetOwnership(EndsWith("id"), kMountUID, kMountGID)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(EndsWith("known_hosts"), kMountUID, kMountGID)) |
| .WillOnce(Return(true)); |
| EXPECT_CALL(platform_, |
| SetOwnership(StartsWith(working_dir_.GetPath().value()), |
| kMountUID, getgid())) |
| .WillOnce(Return(true)); |
| |
| std::vector<std::string> args; |
| EXPECT_EQ( |
| MOUNT_ERROR_NONE, |
| ConfigureSandbox( |
| kSomeSource.value(), |
| {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| |
| EXPECT_THAT( |
| args, |
| UnorderedElementsAre( |
| "KbdInteractiveAuthentication=no", "PasswordAuthentication=no", |
| "BatchMode=yes", "follow_symlinks", "cache=no", "uid=1000", |
| "gid=1001", |
| AllOf(StartsWith("IdentityFile=" + working_dir_.GetPath().value()), |
| EndsWith("/id")), |
| AllOf(StartsWith("UserKnownHostsFile=" + |
| working_dir_.GetPath().value()), |
| EndsWith("/known_hosts")))); |
| |
| std::string id_path; |
| ASSERT_TRUE(GetParamValue(args, "IdentityFile", &id_path)); |
| std::string hosts_path; |
| ASSERT_TRUE(GetParamValue(args, "UserKnownHostsFile", &hosts_path)); |
| |
| base::stat_wrapper_t stat; |
| EXPECT_EQ(0, base::File::Stat(id_path.c_str(), &stat)); |
| EXPECT_EQ(0600, stat.st_mode & 0777); |
| EXPECT_EQ(0, base::File::Stat(hosts_path.c_str(), &stat)); |
| EXPECT_EQ(0600, stat.st_mode & 0777); |
| base::FilePath dir = base::FilePath(id_path).DirName(); |
| EXPECT_EQ(0, base::File::Stat(dir.value().c_str(), &stat)); |
| EXPECT_EQ(0770, stat.st_mode & 0777); |
| |
| std::string data; |
| ASSERT_TRUE(base::ReadFileToString(base::FilePath(id_path), &data)); |
| EXPECT_EQ("abc\n", data); |
| ASSERT_TRUE(base::ReadFileToString(base::FilePath(hosts_path), &data)); |
| EXPECT_EQ("12345\n", data); |
| } |
| |
| TEST_F(SshfsHelperTest, ConfigureSandboxWithHostAndPort) { |
| std::vector<std::string> args; |
| EXPECT_EQ(MOUNT_ERROR_NONE, ConfigureSandbox(kSomeSource.value(), |
| {"IdentityBase64=YWJjCg==", |
| "UserKnownHostsBase64=MTIzNDUK", |
| "HostName=foobar", "Port=1234"}, |
| &args)); |
| |
| EXPECT_THAT(args, |
| IsSupersetOf({StrEq("HostName=foobar"), StrEq("Port=1234")})); |
| } |
| |
| TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithInvalidSource) { |
| EXPECT_CALL(platform_, SetOwnership).Times(0); |
| std::vector<std::string> args; |
| EXPECT_NE( |
| MOUNT_ERROR_NONE, |
| ConfigureSandbox( |
| "foo://bar", |
| {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| } |
| |
| TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutId) { |
| EXPECT_CALL(platform_, SetOwnership).Times(0); |
| std::vector<std::string> args; |
| EXPECT_NE(MOUNT_ERROR_NONE, |
| ConfigureSandbox(kSomeSource.value(), |
| {"UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| } |
| |
| TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutKnownHosts) { |
| EXPECT_CALL(platform_, SetOwnership).Times(0); |
| std::vector<std::string> args; |
| EXPECT_NE(MOUNT_ERROR_NONE, |
| ConfigureSandbox(kSomeSource.value(), {"IdentityBase64=YWJjCg=="}, |
| &args)); |
| } |
| |
| TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutDir) { |
| EXPECT_CALL(platform_, SetOwnership).Times(0); |
| ASSERT_TRUE(working_dir_.Delete()); |
| std::vector<std::string> args; |
| EXPECT_NE( |
| MOUNT_ERROR_NONE, |
| ConfigureSandbox( |
| kSomeSource.value(), |
| {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| } |
| |
| // Verifies that CanMount correctly identifies handleable URIs. |
| TEST_F(SshfsHelperTest, CanMount) { |
| base::FilePath name; |
| |
| EXPECT_TRUE(helper_->CanMount("sshfs://foo", {}, &name)); |
| EXPECT_EQ("foo", name.value()); |
| EXPECT_TRUE(helper_->CanMount("sshfs://", {}, &name)); |
| EXPECT_EQ("sshfs", name.value()); |
| EXPECT_TRUE(helper_->CanMount("sshfs://usr@host.com:", {}, &name)); |
| EXPECT_EQ("usr@host_com:", name.value()); |
| EXPECT_TRUE(helper_->CanMount("sshfs://host:/some/path/..", {}, &name)); |
| EXPECT_EQ("host:$some$path$__", name.value()); |
| |
| EXPECT_FALSE(helper_->CanMount("sshfss://foo", {}, &name)); |
| EXPECT_FALSE(helper_->CanMount("ssh://foo", {}, &name)); |
| } |
| |
| } // namespace cros_disks |