blob: 3b9f642e28682cb4224693ba02d893c92f086bf6 [file] [log] [blame]
Sergei Datsenkobcd8e462018-04-20 15:44:56 +10001// 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 Datsenko71ba50a2020-11-27 14:33:39 +11007#include <sys/stat.h>
8
Sergei Datsenkobcd8e462018-04-20 15:44:56 +10009#include <string>
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110010#include <utility>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100011#include <vector>
12
Qijiang Fan713061e2021-03-08 15:45:12 +090013#include <base/check.h>
14#include <base/check_op.h>
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110015#include <base/files/file_util.h>
16#include <base/files/scoped_temp_dir.h>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100017#include <base/strings/string_split.h>
18#include <base/strings/string_util.h>
19#include <base/strings/stringprintf.h>
Simon Glass2b1da092020-05-21 12:24:16 -060020#include <brillo/process/process_reaper.h>
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100021#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 Hughesd89eea12020-08-24 18:13:31 -070029using testing::_;
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110030using testing::AllOf;
31using testing::EndsWith;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100032using testing::HasSubstr;
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110033using testing::IsSupersetOf;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100034using testing::Not;
35using testing::Return;
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110036using testing::StartsWith;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100037using testing::StrEq;
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110038using testing::UnorderedElementsAre;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100039
40namespace cros_disks {
41
42namespace {
43
44const uid_t kMountUID = 200;
45const gid_t kMountGID = 201;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100046const base::FilePath kWorkingDir("/wkdir");
47const base::FilePath kMountDir("/mnt");
48const Uri kSomeSource("sshfs", "src");
49
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110050std::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 Datsenkobcd8e462018-04-20 15:44:56 +100059// Mock Platform implementation for testing.
60class MockPlatform : public Platform {
61 public:
62 MockPlatform() = default;
63
Ben Chan213c6d92019-04-10 16:21:52 -070064 bool GetUserAndGroupId(const std::string& user,
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100065 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 Datsenkobcd8e462018-04-20 15:44:56 +100074 return false;
75 }
76
Ben Chanef8e6032019-09-27 08:24:56 -070077 MOCK_METHOD(bool,
78 SetOwnership,
79 (const std::string&, uid_t, gid_t),
80 (const, override));
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100081};
82
83} // namespace
84
85class SshfsHelperTest : public ::testing::Test {
86 public:
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110087 SshfsHelperTest() {
Sergei Datsenkobcd8e462018-04-20 15:44:56 +100088 ON_CALL(platform_, SetOwnership(_, kMountUID, getgid()))
89 .WillByDefault(Return(true));
Sergei Datsenko71ba50a2020-11-27 14:33:39 +110090 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 Datsenkobcd8e462018-04-20 15:44:56 +100098 }
99
100 protected:
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100101 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 Datsenkobcd8e462018-04-20 15:44:56 +1000113 MockPlatform platform_;
Sergei Datsenkoa910bba2019-06-18 13:31:59 +1000114 brillo::ProcessReaper process_reaper_;
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100115 base::ScopedTempDir working_dir_;
116 std::unique_ptr<SshfsHelper> helper_;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000117};
118
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100119TEST_F(SshfsHelperTest, ConfigureSandbox) {
120 EXPECT_CALL(platform_, SetOwnership(EndsWith("id"), kMountUID, kMountGID))
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000121 .WillOnce(Return(true));
Sergei Datsenkoad2cb6a2018-05-15 17:34:26 +1000122 EXPECT_CALL(platform_,
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100123 SetOwnership(EndsWith("known_hosts"), kMountUID, kMountGID))
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000124 .WillOnce(Return(true));
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100125 EXPECT_CALL(platform_,
126 SetOwnership(StartsWith(working_dir_.GetPath().value()),
127 kMountUID, getgid()))
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000128 .WillOnce(Return(true));
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100129
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
170TEST_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
182TEST_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
192TEST_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
200TEST_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
208TEST_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 Datsenkobcd8e462018-04-20 15:44:56 +1000217}
218
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000219// Verifies that CanMount correctly identifies handleable URIs.
220TEST_F(SshfsHelperTest, CanMount) {
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100221 base::FilePath name;
Sergei Datsenkobcd8e462018-04-20 15:44:56 +1000222
Sergei Datsenko71ba50a2020-11-27 14:33:39 +1100223 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 Datsenkobcd8e462018-04-20 15:44:56 +1000234}
235
236} // namespace cros_disks