-
Notifications
You must be signed in to change notification settings - Fork 6.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Remote Commands] Add "Remote Powerwash" command
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
Showing
9 changed files
with
338 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
80 changes: 80 additions & 0 deletions
80
chrome/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
37 changes: 37 additions & 0 deletions
37
chrome/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job.h
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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_ |
170 changes: 170 additions & 0 deletions
170
...e/browser/chromeos/policy/remote_commands/device_command_remote_powerwash_job_unittest.cc
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.