Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 1 | // Copyright 2018 The Chromium OS Authors. All rights reserved. |
| 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
| 5 | #include "cros-disks/sshfs_helper.h" |
| 6 | |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 7 | #include <sys/stat.h> |
| 8 | |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 9 | #include <string> |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 10 | #include <utility> |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 11 | #include <vector> |
| 12 | |
Qijiang Fan | 713061e | 2021-03-08 15:45:12 +0900 | [diff] [blame^] | 13 | #include <base/check.h> |
| 14 | #include <base/check_op.h> |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 15 | #include <base/files/file_util.h> |
| 16 | #include <base/files/scoped_temp_dir.h> |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 17 | #include <base/strings/string_split.h> |
| 18 | #include <base/strings/string_util.h> |
| 19 | #include <base/strings/stringprintf.h> |
Simon Glass | 2b1da09 | 2020-05-21 12:24:16 -0600 | [diff] [blame] | 20 | #include <brillo/process/process_reaper.h> |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 21 | #include <gmock/gmock.h> |
| 22 | #include <gtest/gtest.h> |
| 23 | |
| 24 | #include "cros-disks/fuse_mounter.h" |
| 25 | #include "cros-disks/mount_options.h" |
| 26 | #include "cros-disks/platform.h" |
| 27 | #include "cros-disks/uri.h" |
| 28 | |
Tom Hughes | d89eea1 | 2020-08-24 18:13:31 -0700 | [diff] [blame] | 29 | using testing::_; |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 30 | using testing::AllOf; |
| 31 | using testing::EndsWith; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 32 | using testing::HasSubstr; |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 33 | using testing::IsSupersetOf; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 34 | using testing::Not; |
| 35 | using testing::Return; |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 36 | using testing::StartsWith; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 37 | using testing::StrEq; |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 38 | using testing::UnorderedElementsAre; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 39 | |
| 40 | namespace cros_disks { |
| 41 | |
| 42 | namespace { |
| 43 | |
| 44 | const uid_t kMountUID = 200; |
| 45 | const gid_t kMountGID = 201; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 46 | const base::FilePath kWorkingDir("/wkdir"); |
| 47 | const base::FilePath kMountDir("/mnt"); |
| 48 | const Uri kSomeSource("sshfs", "src"); |
| 49 | |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 50 | std::vector<std::string> ParseOptions(const SandboxedProcess& sandbox) { |
| 51 | CHECK_EQ(3, sandbox.arguments().size()); |
| 52 | CHECK_EQ("src", sandbox.arguments()[0]); |
| 53 | CHECK_EQ("-o", sandbox.arguments()[1]); |
| 54 | return base::SplitString(sandbox.arguments()[2], ",", |
| 55 | base::WhitespaceHandling::KEEP_WHITESPACE, |
| 56 | base::SplitResult::SPLIT_WANT_ALL); |
| 57 | } |
| 58 | |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 59 | // Mock Platform implementation for testing. |
| 60 | class MockPlatform : public Platform { |
| 61 | public: |
| 62 | MockPlatform() = default; |
| 63 | |
Ben Chan | 213c6d9 | 2019-04-10 16:21:52 -0700 | [diff] [blame] | 64 | bool GetUserAndGroupId(const std::string& user, |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 65 | uid_t* user_id, |
| 66 | gid_t* group_id) const override { |
| 67 | if (user == "fuse-sshfs") { |
| 68 | if (user_id) |
| 69 | *user_id = kMountUID; |
| 70 | if (group_id) |
| 71 | *group_id = kMountGID; |
| 72 | return true; |
| 73 | } |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 74 | return false; |
| 75 | } |
| 76 | |
Ben Chan | ef8e603 | 2019-09-27 08:24:56 -0700 | [diff] [blame] | 77 | MOCK_METHOD(bool, |
| 78 | SetOwnership, |
| 79 | (const std::string&, uid_t, gid_t), |
| 80 | (const, override)); |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 81 | }; |
| 82 | |
| 83 | } // namespace |
| 84 | |
| 85 | class SshfsHelperTest : public ::testing::Test { |
| 86 | public: |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 87 | SshfsHelperTest() { |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 88 | ON_CALL(platform_, SetOwnership(_, kMountUID, getgid())) |
| 89 | .WillByDefault(Return(true)); |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 90 | ON_CALL(platform_, SetOwnership(_, kMountUID, kMountGID)) |
| 91 | .WillByDefault(Return(true)); |
| 92 | } |
| 93 | |
| 94 | void SetUp() override { |
| 95 | CHECK(working_dir_.CreateUniqueTempDir()); |
| 96 | helper_ = std::make_unique<SshfsHelper>(&platform_, &process_reaper_, |
| 97 | working_dir_.GetPath()); |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 98 | } |
| 99 | |
| 100 | protected: |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 101 | MountErrorType ConfigureSandbox(const std::string& source, |
| 102 | std::vector<std::string> params, |
| 103 | std::vector<std::string>* args) { |
| 104 | FakeSandboxedProcess sandbox; |
| 105 | MountErrorType error = helper_->ConfigureSandbox( |
| 106 | source, kMountDir, std::move(params), &sandbox); |
| 107 | if (error == MOUNT_ERROR_NONE) { |
| 108 | *args = ParseOptions(sandbox); |
| 109 | } |
| 110 | return error; |
| 111 | } |
| 112 | |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 113 | MockPlatform platform_; |
Sergei Datsenko | a910bba | 2019-06-18 13:31:59 +1000 | [diff] [blame] | 114 | brillo::ProcessReaper process_reaper_; |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 115 | base::ScopedTempDir working_dir_; |
| 116 | std::unique_ptr<SshfsHelper> helper_; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 117 | }; |
| 118 | |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 119 | TEST_F(SshfsHelperTest, ConfigureSandbox) { |
| 120 | EXPECT_CALL(platform_, SetOwnership(EndsWith("id"), kMountUID, kMountGID)) |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 121 | .WillOnce(Return(true)); |
Sergei Datsenko | ad2cb6a | 2018-05-15 17:34:26 +1000 | [diff] [blame] | 122 | EXPECT_CALL(platform_, |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 123 | SetOwnership(EndsWith("known_hosts"), kMountUID, kMountGID)) |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 124 | .WillOnce(Return(true)); |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 125 | EXPECT_CALL(platform_, |
| 126 | SetOwnership(StartsWith(working_dir_.GetPath().value()), |
| 127 | kMountUID, getgid())) |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 128 | .WillOnce(Return(true)); |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 129 | |
| 130 | std::vector<std::string> args; |
| 131 | EXPECT_EQ( |
| 132 | MOUNT_ERROR_NONE, |
| 133 | ConfigureSandbox( |
| 134 | kSomeSource.value(), |
| 135 | {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| 136 | |
| 137 | EXPECT_THAT( |
| 138 | args, |
| 139 | UnorderedElementsAre( |
| 140 | "KbdInteractiveAuthentication=no", "PasswordAuthentication=no", |
| 141 | "BatchMode=yes", "follow_symlinks", "cache=no", "uid=1000", |
| 142 | "gid=1001", |
| 143 | AllOf(StartsWith("IdentityFile=" + working_dir_.GetPath().value()), |
| 144 | EndsWith("/id")), |
| 145 | AllOf(StartsWith("UserKnownHostsFile=" + |
| 146 | working_dir_.GetPath().value()), |
| 147 | EndsWith("/known_hosts")))); |
| 148 | |
| 149 | std::string id_path; |
| 150 | ASSERT_TRUE(GetParamValue(args, "IdentityFile", &id_path)); |
| 151 | std::string hosts_path; |
| 152 | ASSERT_TRUE(GetParamValue(args, "UserKnownHostsFile", &hosts_path)); |
| 153 | |
| 154 | base::stat_wrapper_t stat; |
| 155 | EXPECT_EQ(0, base::File::Stat(id_path.c_str(), &stat)); |
| 156 | EXPECT_EQ(0600, stat.st_mode & 0777); |
| 157 | EXPECT_EQ(0, base::File::Stat(hosts_path.c_str(), &stat)); |
| 158 | EXPECT_EQ(0600, stat.st_mode & 0777); |
| 159 | base::FilePath dir = base::FilePath(id_path).DirName(); |
| 160 | EXPECT_EQ(0, base::File::Stat(dir.value().c_str(), &stat)); |
| 161 | EXPECT_EQ(0770, stat.st_mode & 0777); |
| 162 | |
| 163 | std::string data; |
| 164 | ASSERT_TRUE(base::ReadFileToString(base::FilePath(id_path), &data)); |
| 165 | EXPECT_EQ("abc\n", data); |
| 166 | ASSERT_TRUE(base::ReadFileToString(base::FilePath(hosts_path), &data)); |
| 167 | EXPECT_EQ("12345\n", data); |
| 168 | } |
| 169 | |
| 170 | TEST_F(SshfsHelperTest, ConfigureSandboxWithHostAndPort) { |
| 171 | std::vector<std::string> args; |
| 172 | EXPECT_EQ(MOUNT_ERROR_NONE, ConfigureSandbox(kSomeSource.value(), |
| 173 | {"IdentityBase64=YWJjCg==", |
| 174 | "UserKnownHostsBase64=MTIzNDUK", |
| 175 | "HostName=foobar", "Port=1234"}, |
| 176 | &args)); |
| 177 | |
| 178 | EXPECT_THAT(args, |
| 179 | IsSupersetOf({StrEq("HostName=foobar"), StrEq("Port=1234")})); |
| 180 | } |
| 181 | |
| 182 | TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithInvalidSource) { |
| 183 | EXPECT_CALL(platform_, SetOwnership).Times(0); |
| 184 | std::vector<std::string> args; |
| 185 | EXPECT_NE( |
| 186 | MOUNT_ERROR_NONE, |
| 187 | ConfigureSandbox( |
| 188 | "foo://bar", |
| 189 | {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| 190 | } |
| 191 | |
| 192 | TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutId) { |
| 193 | EXPECT_CALL(platform_, SetOwnership).Times(0); |
| 194 | std::vector<std::string> args; |
| 195 | EXPECT_NE(MOUNT_ERROR_NONE, |
| 196 | ConfigureSandbox(kSomeSource.value(), |
| 197 | {"UserKnownHostsBase64=MTIzNDUK"}, &args)); |
| 198 | } |
| 199 | |
| 200 | TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutKnownHosts) { |
| 201 | EXPECT_CALL(platform_, SetOwnership).Times(0); |
| 202 | std::vector<std::string> args; |
| 203 | EXPECT_NE(MOUNT_ERROR_NONE, |
| 204 | ConfigureSandbox(kSomeSource.value(), {"IdentityBase64=YWJjCg=="}, |
| 205 | &args)); |
| 206 | } |
| 207 | |
| 208 | TEST_F(SshfsHelperTest, ConfigureSandboxFailsWithoutDir) { |
| 209 | EXPECT_CALL(platform_, SetOwnership).Times(0); |
| 210 | ASSERT_TRUE(working_dir_.Delete()); |
| 211 | std::vector<std::string> args; |
| 212 | EXPECT_NE( |
| 213 | MOUNT_ERROR_NONE, |
| 214 | ConfigureSandbox( |
| 215 | kSomeSource.value(), |
| 216 | {"IdentityBase64=YWJjCg==", "UserKnownHostsBase64=MTIzNDUK"}, &args)); |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 217 | } |
| 218 | |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 219 | // Verifies that CanMount correctly identifies handleable URIs. |
| 220 | TEST_F(SshfsHelperTest, CanMount) { |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 221 | base::FilePath name; |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 222 | |
Sergei Datsenko | 71ba50a | 2020-11-27 14:33:39 +1100 | [diff] [blame] | 223 | EXPECT_TRUE(helper_->CanMount("sshfs://foo", {}, &name)); |
| 224 | EXPECT_EQ("foo", name.value()); |
| 225 | EXPECT_TRUE(helper_->CanMount("sshfs://", {}, &name)); |
| 226 | EXPECT_EQ("sshfs", name.value()); |
| 227 | EXPECT_TRUE(helper_->CanMount("sshfs://usr@host.com:", {}, &name)); |
| 228 | EXPECT_EQ("usr@host_com:", name.value()); |
| 229 | EXPECT_TRUE(helper_->CanMount("sshfs://host:/some/path/..", {}, &name)); |
| 230 | EXPECT_EQ("host:$some$path$__", name.value()); |
| 231 | |
| 232 | EXPECT_FALSE(helper_->CanMount("sshfss://foo", {}, &name)); |
| 233 | EXPECT_FALSE(helper_->CanMount("ssh://foo", {}, &name)); |
Sergei Datsenko | bcd8e46 | 2018-04-20 15:44:56 +1000 | [diff] [blame] | 234 | } |
| 235 | |
| 236 | } // namespace cros_disks |