Skip to content

Commit

Permalink
[Remote Commands] Add "Remote Powerwash" command
Browse files Browse the repository at this point in the history
Currently managed Chrome OS devices support remote commands issued by the admin
on CPanel.  Add a command that starts a powerwash on the device when received.

Design doc @ go/remote-powerwash-command

Bug: 891222
Change-Id: I8aab58db3de12681fd655642e984cff6f24e392f
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1729219
Reviewed-by: Julian Pastarmov <pastarmovj@chromium.org>
Reviewed-by: Ryo Hashimoto <hashimoto@chromium.org>
Commit-Queue: Ivan Šandrk <isandrk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#686378}
  • Loading branch information
Ivan Sandrk authored and Commit Bot committed Aug 13, 2019
1 parent 35a03a6 commit 19d4547
Show file tree
Hide file tree
Showing 9 changed files with 338 additions and 0 deletions.
3 changes: 3 additions & 0 deletions chrome/browser/chromeos/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -1712,6 +1712,8 @@ source_set("chromeos") {
"policy/remote_commands/device_command_reboot_job.h",
"policy/remote_commands/device_command_refresh_machine_certificate_job.cc",
"policy/remote_commands/device_command_refresh_machine_certificate_job.h",
"policy/remote_commands/device_command_remote_powerwash_job.cc",
"policy/remote_commands/device_command_remote_powerwash_job.h",
"policy/remote_commands/device_command_screenshot_job.cc",
"policy/remote_commands/device_command_screenshot_job.h",
"policy/remote_commands/device_command_set_volume_job.cc",
Expand Down Expand Up @@ -2629,6 +2631,7 @@ source_set("unit_tests") {
"policy/off_hours/off_hours_policy_applier_unittest.cc",
"policy/off_hours/off_hours_proto_parser_unittest.cc",
"policy/pre_signin_policy_fetcher_unittest.cc",
"policy/remote_commands/device_command_remote_powerwash_job_unittest.cc",
"policy/remote_commands/device_command_screenshot_job_unittest.cc",
"policy/remote_commands/device_command_set_volume_job_unittest.cc",
"policy/remote_commands/device_command_start_crd_session_unittest.cc",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// Copyright 2019 The Chromium 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 "chrome/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job.h"

#include <utility>

#include "base/bind.h"
#include "base/syslog_logging.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/policy/core/common/remote_commands/remote_commands_service.h"
#include "components/policy/proto/device_management_backend.pb.h"

namespace policy {

namespace {

// Expiration time for the command is this high because this command is supposed
// to be a security feature where the device gets wiped even if it's turned on
// again only after several years of being powered off.
constexpr base::TimeDelta kRemotePowerwashCommandExpirationTime =
base::TimeDelta::FromDays(5 * 365); // 5 years.

// The time that we wait for the server to get the ACK, if that passes we
// immediately start the powerwash process.
constexpr base::TimeDelta kFailsafeTimerTimeout =
base::TimeDelta::FromSeconds(1);

void StartPowerwash(enterprise_management::SignedData signed_command) {
chromeos::SessionManagerClient::Get()->StartRemoteDeviceWipe(signed_command);
}

} // namespace

DeviceCommandRemotePowerwashJob::DeviceCommandRemotePowerwashJob(
RemoteCommandsService* service)
: service_(service) {}

DeviceCommandRemotePowerwashJob::~DeviceCommandRemotePowerwashJob() = default;

enterprise_management::RemoteCommand_Type
DeviceCommandRemotePowerwashJob::GetType() const {
return enterprise_management::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH;
}

bool DeviceCommandRemotePowerwashJob::IsExpired(base::TimeTicks now) {
return now > issued_time() + kRemotePowerwashCommandExpirationTime;
}

void DeviceCommandRemotePowerwashJob::RunImpl(
CallbackWithResult succeeded_callback,
CallbackWithResult failed_callback) {
// Don't support unsigned remote powerwash command.
if (!signed_command()) {
SYSLOG(ERROR) << "Unsigned remote powerwash command received, aborting.";
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(failed_callback), nullptr));
}

// Set callback which gets called after command is ACKd to the server. We want
// to start the powerwash process only after the server got the ACK, otherwise
// we could reboot before ACKing and then the server would never get the ACK.
service_->SetOnCommandAckedCallback(
base::BindOnce(&StartPowerwash, signed_command().value()));

// Also set a failsafe timer that starts the powerwash so a faulty network
// connection doesn't prevent the powerwash from happening.
base::SequencedTaskRunnerHandle::Get()->PostDelayedTask(
FROM_HERE, base::BindOnce(&StartPowerwash, signed_command().value()),
kFailsafeTimerTimeout);

// Ack the command.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(succeeded_callback), nullptr));
}

} // namespace policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef CHROME_BROWSER_CHROMEOS_POLICY_REMOTE_COMMANDS_DEVICE_COMMAND_REMOTE_POWERWASH_JOB_H_
#define CHROME_BROWSER_CHROMEOS_POLICY_REMOTE_COMMANDS_DEVICE_COMMAND_REMOTE_POWERWASH_JOB_H_

#include "base/macros.h"
#include "components/policy/core/common/remote_commands/remote_command_job.h"

namespace policy {

class RemoteCommandsService;

class DeviceCommandRemotePowerwashJob : public RemoteCommandJob {
public:
explicit DeviceCommandRemotePowerwashJob(RemoteCommandsService* service);
~DeviceCommandRemotePowerwashJob() override;

// RemoteCommandJob:
enterprise_management::RemoteCommand_Type GetType() const override;

protected:
// RemoteCommandJob:
bool IsExpired(base::TimeTicks now) override;
void RunImpl(CallbackWithResult succeeded_callback,
CallbackWithResult failed_callback) override;

private:
RemoteCommandsService* const service_;

DISALLOW_COPY_AND_ASSIGN(DeviceCommandRemotePowerwashJob);
};

} // namespace policy

#endif // CHROME_BROWSER_CHROMEOS_POLICY_REMOTE_COMMANDS_DEVICE_COMMAND_REMOTE_POWERWASH_JOB_H_
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2019 The Chromium 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 "chrome/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job.h"

#include <memory>
#include <utility>

#include "base/location.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/test_mock_time_task_runner.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/time/time.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_commands_factory_chromeos.h"
#include "chromeos/dbus/session_manager/fake_session_manager_client.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_client.h"
#include "components/policy/core/common/remote_commands/remote_command_job.h"
#include "components/policy/core/common/remote_commands/remote_commands_service.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace policy {

constexpr base::TimeDelta kCommandAge = base::TimeDelta::FromMinutes(10);
constexpr base::TimeDelta kVeryoldCommandAge =
base::TimeDelta::FromDays(5 * 365 - 1);

class TestingRemoteCommandsService : public RemoteCommandsService {
public:
explicit TestingRemoteCommandsService(MockCloudPolicyClient* client)
: RemoteCommandsService(std::make_unique<DeviceCommandsFactoryChromeOS>(
nullptr /* policy_manager */),
client,
nullptr /* store */) {}
// RemoteCommandsService:
void SetOnCommandAckedCallback(base::OnceClosure callback) override {
on_command_acked_callback_ = std::move(callback);
}

base::OnceClosure OnCommandAckedCallback() {
return std::move(on_command_acked_callback_);
}

protected:
base::OnceClosure on_command_acked_callback_;

private:
DISALLOW_COPY_AND_ASSIGN(TestingRemoteCommandsService);
};

std::unique_ptr<policy::RemoteCommandJob> CreateRemotePowerwashJob(
base::TimeDelta age_of_command,
RemoteCommandsService* service) {
// Create the job proto.
enterprise_management::RemoteCommand command_proto;
command_proto.set_type(
enterprise_management::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH);
constexpr policy::RemoteCommandJob::UniqueIDType kUniqueID = 123456789;
command_proto.set_command_id(kUniqueID);
command_proto.set_age_of_command(age_of_command.InMilliseconds());

// Create the job and validate.
auto job = std::make_unique<policy::DeviceCommandRemotePowerwashJob>(service);

enterprise_management::SignedData signed_command;
EXPECT_TRUE(
job->Init(base::TimeTicks::Now(), command_proto, &signed_command));
EXPECT_EQ(kUniqueID, job->unique_id());
EXPECT_EQ(policy::RemoteCommandJob::NOT_STARTED, job->status());

return job;
}

class DeviceCommandRemotePowerwashJobTest : public testing::Test {
protected:
DeviceCommandRemotePowerwashJobTest();
~DeviceCommandRemotePowerwashJobTest() override;

scoped_refptr<base::TestMockTimeTaskRunner> task_runner_;
base::RunLoop run_loop_;

const std::unique_ptr<MockCloudPolicyClient> client_;
const std::unique_ptr<TestingRemoteCommandsService> service_;

private:
DISALLOW_COPY_AND_ASSIGN(DeviceCommandRemotePowerwashJobTest);
};

DeviceCommandRemotePowerwashJobTest::DeviceCommandRemotePowerwashJobTest()
: task_runner_(base::MakeRefCounted<base::TestMockTimeTaskRunner>(
base::TestMockTimeTaskRunner::Type::kBoundToThread)),
client_(std::make_unique<MockCloudPolicyClient>()),
service_(std::make_unique<TestingRemoteCommandsService>(client_.get())) {
chromeos::SessionManagerClient::InitializeFakeInMemory();
}

DeviceCommandRemotePowerwashJobTest::~DeviceCommandRemotePowerwashJobTest() {
chromeos::SessionManagerClient::Shutdown();
}

// Make sure that the command is still valid 5*365-1 days after being issued.
TEST_F(DeviceCommandRemotePowerwashJobTest, TestCommandLifetime) {
std::unique_ptr<policy::RemoteCommandJob> job =
CreateRemotePowerwashJob(kVeryoldCommandAge, service_.get());

EXPECT_TRUE(job->Run(base::TimeTicks::Now(), base::OnceClosure()));
}

// Make sure that powerwash starts once the command gets ACK'd to the server.
TEST_F(DeviceCommandRemotePowerwashJobTest, TestCommandAckStartsPowerwash) {
std::unique_ptr<policy::RemoteCommandJob> job =
CreateRemotePowerwashJob(kCommandAge, service_.get());

// No powerwash at this point.
EXPECT_EQ(0, chromeos::FakeSessionManagerClient::Get()
->start_device_wipe_call_count());

EXPECT_TRUE(job->Run(base::TimeTicks::Now(), run_loop_.QuitClosure()));
// At this point the job is run, and the succeeded_callback is waiting to be
// invoked.

run_loop_.Run();
// Now the succeeded_callback has been invoked, and normally it would cause an
// ACK to be sent to the server, and upon receiving a response back from the
// server the powerwash would start.

base::RunLoop run_loop2;
chromeos::FakeSessionManagerClient::Get()->set_on_start_device_wipe_callback(
base::BindLambdaForTesting([&]() { run_loop2.Quit(); }));

// Simulate a response from the server by posting a task and waiting for
// StartDeviceWipe to be called.
base::SequencedTaskRunnerHandle::Get()->PostTask(
FROM_HERE, service_->OnCommandAckedCallback());
run_loop2.Run();

// One powerwash coming up.
EXPECT_EQ(1, chromeos::FakeSessionManagerClient::Get()
->start_device_wipe_call_count());
}

// Make sure that the failsafe timer starts the powerwash in case of no ACK.
TEST_F(DeviceCommandRemotePowerwashJobTest, TestFailsafeTimerStartsPowerwash) {
std::unique_ptr<policy::RemoteCommandJob> job =
CreateRemotePowerwashJob(kCommandAge, service_.get());

// No powerwash at this point.
EXPECT_EQ(0, chromeos::FakeSessionManagerClient::Get()
->start_device_wipe_call_count());

// Run job + succeeded_callback.
EXPECT_TRUE(job->Run(base::TimeTicks::Now(), run_loop_.QuitClosure()));
run_loop_.Run();

// After 500ms the timer is not run yet.
task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(500));
EXPECT_EQ(0, chromeos::FakeSessionManagerClient::Get()
->start_device_wipe_call_count());

// After 1s the timer is run.
task_runner_->FastForwardBy(base::TimeDelta::FromMilliseconds(500));
EXPECT_EQ(1, chromeos::FakeSessionManagerClient::Get()
->start_device_wipe_call_count());
}

} // namespace policy
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "chrome/browser/chromeos/policy/remote_commands/device_command_fetch_status_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_reboot_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_refresh_machine_certificate_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_screenshot_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_set_volume_job.h"
#include "chrome/browser/chromeos/policy/remote_commands/device_command_start_crd_session_job.h"
Expand Down Expand Up @@ -51,6 +52,8 @@ DeviceCommandsFactoryChromeOS::BuildJobForType(em::RemoteCommand_Type type,
case em::RemoteCommand_Type_DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE:
return std::make_unique<DeviceCommandRefreshMachineCertificateJob>(
policy_manager_->GetMachineCertificateUploader());
case em::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH:
return std::make_unique<DeviceCommandRemotePowerwashJob>(service);
default:
// Other types of commands should be sent to UserCommandsFactoryChromeOS
// instead of here.
Expand Down
18 changes: 18 additions & 0 deletions chromeos/dbus/session_manager/fake_session_manager_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -316,6 +316,19 @@ void FakeSessionManagerClient::StopSession() {

void FakeSessionManagerClient::StartDeviceWipe() {
start_device_wipe_call_count_++;
if (!on_start_device_wipe_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(on_start_device_wipe_callback_));
}
}

void FakeSessionManagerClient::StartRemoteDeviceWipe(
const enterprise_management::SignedData& signed_command) {
start_device_wipe_call_count_++;
if (!on_start_device_wipe_callback_.is_null()) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, std::move(on_start_device_wipe_callback_));
}
}

void FakeSessionManagerClient::ClearForcedReEnrollmentVpd(
Expand Down Expand Up @@ -741,4 +754,9 @@ void FakeSessionManagerClient::HandleOwnerKeySet(
std::move(callback_to_run).Run();
}

void FakeSessionManagerClient::set_on_start_device_wipe_callback(
base::OnceClosure callback) {
on_start_device_wipe_callback_ = std::move(callback);
}

} // namespace chromeos
6 changes: 6 additions & 0 deletions chromeos/dbus/session_manager/fake_session_manager_client.h
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ class COMPONENT_EXPORT(SESSION_MANAGER) FakeSessionManagerClient
const cryptohome::AccountIdentifier& cryptohome_id) override;
void StopSession() override;
void StartDeviceWipe() override;
void StartRemoteDeviceWipe(
const enterprise_management::SignedData& signed_command) override;
void ClearForcedReEnrollmentVpd(VoidDBusMethodCallback callback) override;
void StartTPMFirmwareUpdate(const std::string& update_mode) override;
void RequestLockScreen() override;
Expand Down Expand Up @@ -214,6 +216,7 @@ class COMPONENT_EXPORT(SESSION_MANAGER) FakeSessionManagerClient
return clear_forced_re_enrollment_vpd_call_count_;
}

void set_on_start_device_wipe_callback(base::OnceClosure callback);
int start_device_wipe_call_count() const {
return start_device_wipe_call_count_;
}
Expand Down Expand Up @@ -292,6 +295,9 @@ class COMPONENT_EXPORT(SESSION_MANAGER) FakeSessionManagerClient
bool force_retrieve_policy_load_error_ = false;

int clear_forced_re_enrollment_vpd_call_count_ = 0;
// Callback which is run after calling |StartDeviceWipe| or
// |StartRemoteDeviceWipe|.
base::OnceClosure on_start_device_wipe_callback_;
int start_device_wipe_call_count_ = 0;
int request_lock_screen_call_count_ = 0;
int notify_lock_screen_shown_call_count_ = 0;
Expand Down
12 changes: 12 additions & 0 deletions chromeos/dbus/session_manager/session_manager_client.cc
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,18 @@ class SessionManagerClientImpl : public SessionManagerClient {
login_manager::kSessionManagerStartDeviceWipe);
}

void StartRemoteDeviceWipe(
const enterprise_management::SignedData& signed_command) override {
dbus::MethodCall method_call(
login_manager::kSessionManagerInterface,
login_manager::kSessionManagerStartRemoteDeviceWipe);
dbus::MessageWriter writer(&method_call);
writer.AppendProtoAsArrayOfBytes(signed_command);
session_manager_proxy_->CallMethod(&method_call,
dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::DoNothing());
}

void ClearForcedReEnrollmentVpd(VoidDBusMethodCallback callback) override {
dbus::MethodCall method_call(
login_manager::kSessionManagerInterface,
Expand Down

0 comments on commit 19d4547

Please sign in to comment.