blob: 907f4a4af52712a1da0567a1c41530d8445c0e15 [file] [log] [blame]
// 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/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