diff options
Diffstat (limited to 'chromium/components/policy/core/common/remote_commands/remote_commands_service.cc')
-rw-r--r-- | chromium/components/policy/core/common/remote_commands/remote_commands_service.cc | 418 |
1 files changed, 418 insertions, 0 deletions
diff --git a/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc b/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc new file mode 100644 index 00000000000..9e05b9920d5 --- /dev/null +++ b/chromium/components/policy/core/common/remote_commands/remote_commands_service.cc @@ -0,0 +1,418 @@ +// Copyright 2015 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 "components/policy/core/common/remote_commands/remote_commands_service.h" + +#include <algorithm> +#include <memory> +#include <string> +#include <utility> + +#include "base/bind.h" +#include "base/callback.h" +#include "base/containers/contains.h" +#include "base/metrics/histogram_functions.h" +#include "base/strings/stringprintf.h" +#include "base/syslog_logging.h" +#include "components/policy/core/common/cloud/cloud_policy_client.h" +#include "components/policy/core/common/cloud/cloud_policy_constants.h" +#include "components/policy/core/common/cloud/cloud_policy_store.h" +#include "components/policy/core/common/cloud/cloud_policy_validator.h" +#include "components/policy/core/common/cloud/enterprise_metrics.h" +#include "components/policy/core/common/remote_commands/remote_commands_factory.h" + +namespace policy { + +namespace em = enterprise_management; + +namespace { + +RemoteCommandsService::MetricReceivedRemoteCommand RemoteCommandMetricFromType( + em::RemoteCommand_Type type) { + using Metric = RemoteCommandsService::MetricReceivedRemoteCommand; + + switch (type) { + case em::RemoteCommand_Type_COMMAND_ECHO_TEST: + return Metric::kCommandEchoTest; + case em::RemoteCommand_Type_DEVICE_REBOOT: + return Metric::kDeviceReboot; + case em::RemoteCommand_Type_DEVICE_SCREENSHOT: + return Metric::kDeviceScreenshot; + case em::RemoteCommand_Type_DEVICE_SET_VOLUME: + return Metric::kDeviceSetVolume; + case em::RemoteCommand_Type_DEVICE_START_CRD_SESSION: + return Metric::kDeviceStartCrdSession; + case em::RemoteCommand_Type_DEVICE_FETCH_STATUS: + return Metric::kDeviceFetchStatus; + case em::RemoteCommand_Type_USER_ARC_COMMAND: + return Metric::kUserArcCommand; + case em::RemoteCommand_Type_DEVICE_WIPE_USERS: + return Metric::kDeviceWipeUsers; + case em::RemoteCommand_Type_DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE: + return Metric::kDeviceRefreshEnterpriseMachineCertificate; + case em::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH: + return Metric::kDeviceRemotePowerwash; + case em::RemoteCommand_Type_DEVICE_GET_AVAILABLE_DIAGNOSTIC_ROUTINES: + return Metric::kDeviceGetAvailableDiagnosticRoutines; + case em::RemoteCommand_Type_DEVICE_RUN_DIAGNOSTIC_ROUTINE: + return Metric::kDeviceRunDiagnosticRoutine; + case em::RemoteCommand_Type_DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE: + return Metric::kDeviceGetDiagnosticRoutineUpdate; + case em::RemoteCommand_Type_BROWSER_CLEAR_BROWSING_DATA: + return Metric::kBrowserClearBrowsingData; + case em::RemoteCommand_Type_DEVICE_RESET_EUICC: + return Metric::kDeviceResetEuicc; + case em::RemoteCommand_Type_BROWSER_ROTATE_ATTESTATION_CREDENTIAL: + return Metric::kBrowserRotateAttestationCredential; + } + + // None of possible types matched. May indicate that there is new unhandled + // command type. + NOTREACHED() << "Unknown command type to record: " << type; + return Metric::kUnknownType; +} + +const char* RemoteCommandTypeToString(em::RemoteCommand_Type type) { + switch (type) { + case em::RemoteCommand_Type_COMMAND_ECHO_TEST: + return "CommandEchoTest"; + case em::RemoteCommand_Type_DEVICE_REBOOT: + return "DeviceReboot"; + case em::RemoteCommand_Type_DEVICE_SCREENSHOT: + return "DeviceScreenshot"; + case em::RemoteCommand_Type_DEVICE_SET_VOLUME: + return "DeviceSetVolume"; + case em::RemoteCommand_Type_DEVICE_START_CRD_SESSION: + return "DeviceStartCrdSession"; + case em::RemoteCommand_Type_DEVICE_FETCH_STATUS: + return "DeviceFetchStatus"; + case em::RemoteCommand_Type_USER_ARC_COMMAND: + return "UserArcCommand"; + case em::RemoteCommand_Type_DEVICE_WIPE_USERS: + return "DeviceWipeUsers"; + case em::RemoteCommand_Type_DEVICE_REFRESH_ENTERPRISE_MACHINE_CERTIFICATE: + return "DeviceRefreshEnterpriseMachineCertificate"; + case em::RemoteCommand_Type_DEVICE_REMOTE_POWERWASH: + return "DeviceRemotePowerwash"; + case em::RemoteCommand_Type_DEVICE_GET_AVAILABLE_DIAGNOSTIC_ROUTINES: + return "DeviceGetAvailableDiagnosticRoutines"; + case em::RemoteCommand_Type_DEVICE_RUN_DIAGNOSTIC_ROUTINE: + return "DeviceRunDiagnosticRoutine"; + case em::RemoteCommand_Type_DEVICE_GET_DIAGNOSTIC_ROUTINE_UPDATE: + return "DeviceGetDiagnosticRoutineUpdate"; + case em::RemoteCommand_Type_BROWSER_CLEAR_BROWSING_DATA: + return "BrowserClearBrowsingData"; + case em::RemoteCommand_Type_DEVICE_RESET_EUICC: + return "DeviceResetEuicc"; + case em::RemoteCommand_Type_BROWSER_ROTATE_ATTESTATION_CREDENTIAL: + return "BrowserRotateAttestationCredential"; + } + + NOTREACHED() << "Unknown command type: " << type; + return ""; +} + +em::RemoteCommandResult::ResultType CommandStatusToResultType( + RemoteCommandJob::Status status) { + switch (status) { + case RemoteCommandJob::SUCCEEDED: + return em::RemoteCommandResult_ResultType_RESULT_SUCCESS; + case RemoteCommandJob::FAILED: + return em::RemoteCommandResult_ResultType_RESULT_FAILURE; + case RemoteCommandJob::EXPIRED: + case RemoteCommandJob::INVALID: + return em::RemoteCommandResult_ResultType_RESULT_IGNORED; + case RemoteCommandJob::NOT_INITIALIZED: + case RemoteCommandJob::NOT_STARTED: + case RemoteCommandJob::RUNNING: + case RemoteCommandJob::TERMINATED: + case RemoteCommandJob::STATUS_TYPE_SIZE: + NOTREACHED(); + return em::RemoteCommandResult_ResultType_RESULT_IGNORED; + } + NOTREACHED(); + return em::RemoteCommandResult_ResultType_RESULT_IGNORED; +} +} // namespace + +// static +const char* RemoteCommandsService::GetMetricNameReceivedRemoteCommand( + PolicyInvalidationScope scope) { + switch (scope) { + case PolicyInvalidationScope::kUser: + return kMetricUserRemoteCommandReceived; + case PolicyInvalidationScope::kDevice: + return kMetricDeviceRemoteCommandReceived; + case PolicyInvalidationScope::kCBCM: + return kMetricCBCMRemoteCommandReceived; + case PolicyInvalidationScope::kDeviceLocalAccount: + NOTREACHED() << "Unexpected instance of remote commands service with " + "device local account scope."; + return ""; + } +} + +// static +std::string RemoteCommandsService::GetMetricNameExecutedRemoteCommand( + PolicyInvalidationScope scope, + em::RemoteCommand_Type command_type) { + const char* base_metric_name = nullptr; + switch (scope) { + case PolicyInvalidationScope::kUser: + base_metric_name = kMetricUserRemoteCommandExecutedTemplate; + break; + case PolicyInvalidationScope::kDevice: + base_metric_name = kMetricDeviceRemoteCommandExecutedTemplate; + break; + case PolicyInvalidationScope::kCBCM: + base_metric_name = kMetricCBCMRemoteCommandExecutedTemplate; + break; + case PolicyInvalidationScope::kDeviceLocalAccount: + NOTREACHED() << "Unexpected instance of remote commands service with " + "device local account scope."; + return ""; + } + + DCHECK(base_metric_name); + return base::StringPrintf(base_metric_name, + RemoteCommandTypeToString(command_type)); +} + +RemoteCommandsService::RemoteCommandsService( + std::unique_ptr<RemoteCommandsFactory> factory, + CloudPolicyClient* client, + CloudPolicyStore* store, + PolicyInvalidationScope scope) + : factory_(std::move(factory)), + client_(client), + store_(store), + scope_(scope) { + DCHECK(client_); + queue_.AddObserver(this); +} + +RemoteCommandsService::~RemoteCommandsService() { + queue_.RemoveObserver(this); +} + +bool RemoteCommandsService::FetchRemoteCommands() { + if (!client_->is_registered()) { + SYSLOG(WARNING) << "Client is not registered."; + return false; + } + + if (command_fetch_in_progress_) { + has_enqueued_fetch_request_ = true; + return false; + } + + command_fetch_in_progress_ = true; + has_enqueued_fetch_request_ = false; + + std::vector<em::RemoteCommandResult> previous_results; + unsent_results_.swap(previous_results); + + std::unique_ptr<RemoteCommandJob::UniqueIDType> id_to_acknowledge; + + if (has_finished_command_) { + // Acknowledges |lastest_finished_command_id_|, and removes it and every + // command before it from |fetched_command_ids_|. + id_to_acknowledge = std::make_unique<RemoteCommandJob::UniqueIDType>( + lastest_finished_command_id_); + // It's safe to remove these IDs from |fetched_command_ids_| here, since + // it is guaranteed that there is no earlier fetch request in progress + // anymore that could have returned these IDs. + while (!fetched_command_ids_.empty() && + fetched_command_ids_.front() != lastest_finished_command_id_) { + fetched_command_ids_.pop_front(); + } + } + + client_->FetchRemoteCommands( + std::move(id_to_acknowledge), previous_results, + base::BindOnce(&RemoteCommandsService::OnRemoteCommandsFetched, + weak_factory_.GetWeakPtr())); + + return true; +} + +void RemoteCommandsService::SetClocksForTesting( + const base::Clock* clock, + const base::TickClock* tick_clock) { + queue_.SetClocksForTesting(clock, tick_clock); +} + +void RemoteCommandsService::SetOnCommandAckedCallback( + base::OnceClosure callback) { + on_command_acked_callback_ = std::move(callback); +} + +void RemoteCommandsService::VerifyAndEnqueueSignedCommand( + const em::SignedData& signed_command) { + const bool valid_signature = CloudPolicyValidatorBase::VerifySignature( + signed_command.data(), store_->policy_signature_public_key(), + signed_command.signature(), + CloudPolicyValidatorBase::SignatureType::SHA1); + + auto ignore_result = base::BindOnce( + [](RemoteCommandsService* self, const char* error_msg, + MetricReceivedRemoteCommand metric) { + SYSLOG(ERROR) << error_msg; + em::RemoteCommandResult result; + result.set_result(em::RemoteCommandResult_ResultType_RESULT_IGNORED); + result.set_command_id(-1); + self->unsent_results_.push_back(result); + self->RecordReceivedRemoteCommand(metric); + // Trigger another fetch so the results are uploaded. + self->FetchRemoteCommands(); + }, + base::Unretained(this)); + + if (!valid_signature) { + std::move(ignore_result) + .Run("Secure remote command signature verification failed", + MetricReceivedRemoteCommand::kInvalidSignature); + return; + } + + em::PolicyData policy_data; + if (!policy_data.ParseFromString(signed_command.data()) || + !policy_data.has_policy_type() || + policy_data.policy_type() != + dm_protocol::kChromeRemoteCommandPolicyType) { + std::move(ignore_result) + .Run("Secure remote command with wrong PolicyData type", + MetricReceivedRemoteCommand::kInvalid); + return; + } + + em::RemoteCommand command; + if (!policy_data.has_policy_value() || + !command.ParseFromString(policy_data.policy_value())) { + std::move(ignore_result) + .Run("Secure remote command invalid RemoteCommand data", + MetricReceivedRemoteCommand::kInvalid); + return; + } + + const em::PolicyData* const policy = store_->policy(); + if (!policy || policy->device_id() != command.target_device_id()) { + std::move(ignore_result) + .Run("Secure remote command wrong target device id", + MetricReceivedRemoteCommand::kInvalid); + return; + } + + // Signature verification passed. + EnqueueCommand(command, signed_command); +} + +void RemoteCommandsService::EnqueueCommand( + const em::RemoteCommand& command, + const em::SignedData& signed_command) { + if (!command.has_type() || !command.has_command_id()) { + SYSLOG(ERROR) << "Invalid remote command from server."; + const auto metric = !command.has_command_id() + ? MetricReceivedRemoteCommand::kInvalid + : MetricReceivedRemoteCommand::kUnknownType; + RecordReceivedRemoteCommand(metric); + return; + } + + // If the command is already fetched, ignore it. + if (base::Contains(fetched_command_ids_, command.command_id())) { + RecordReceivedRemoteCommand(MetricReceivedRemoteCommand::kDuplicated); + return; + } + + fetched_command_ids_.push_back(command.command_id()); + + std::unique_ptr<RemoteCommandJob> job = + factory_->BuildJobForType(command.type(), this); + + if (!job || !job->Init(queue_.GetNowTicks(), command, signed_command)) { + SYSLOG(ERROR) << "Initialization of remote command type " << command.type() + << " with id " << command.command_id() << " failed."; + const auto metric = job == nullptr + ? MetricReceivedRemoteCommand::kInvalidScope + : MetricReceivedRemoteCommand::kInvalid; + RecordReceivedRemoteCommand(metric); + em::RemoteCommandResult ignored_result; + ignored_result.set_result( + em::RemoteCommandResult_ResultType_RESULT_IGNORED); + ignored_result.set_command_id(command.command_id()); + unsent_results_.push_back(ignored_result); + return; + } + + RecordReceivedRemoteCommand(RemoteCommandMetricFromType(command.type())); + + queue_.AddJob(std::move(job)); +} + +void RemoteCommandsService::OnJobStarted(RemoteCommandJob* command) {} + +void RemoteCommandsService::OnJobFinished(RemoteCommandJob* command) { + has_finished_command_ = true; + lastest_finished_command_id_ = command->unique_id(); + // TODO(binjin): Attempt to sync |lastest_finished_command_id_| to some + // persistent source, so that we can reload it later without relying solely on + // the server to keep our last acknowledged command ID. + // See http://crbug.com/466572. + + em::RemoteCommandResult result; + result.set_command_id(command->unique_id()); + result.set_timestamp(command->execution_started_time().ToJavaTime()); + result.set_result(CommandStatusToResultType(command->status())); + + std::unique_ptr<std::string> result_payload = command->GetResultPayload(); + if (result_payload) + result.set_payload(std::move(*result_payload)); + + SYSLOG(INFO) << "Remote command " << command->unique_id() + << " finished with result " << result.result(); + + unsent_results_.push_back(result); + + RecordExecutedRemoteCommand(*command); + + FetchRemoteCommands(); +} + +void RemoteCommandsService::OnRemoteCommandsFetched( + DeviceManagementStatus status, + const std::vector<enterprise_management::SignedData>& commands) { + DCHECK(command_fetch_in_progress_); + command_fetch_in_progress_ = false; + + if (!on_command_acked_callback_.is_null()) + std::move(on_command_acked_callback_).Run(); + + // TODO(binjin): Add retrying on errors. See http://crbug.com/466572. + if (status == DM_STATUS_SUCCESS) { + for (const auto& command : commands) + VerifyAndEnqueueSignedCommand(command); + } + + // Start another fetch request job immediately if there are unsent command + // results or enqueued fetch requests. + if (!unsent_results_.empty() || has_enqueued_fetch_request_) + FetchRemoteCommands(); +} + +void RemoteCommandsService::RecordReceivedRemoteCommand( + RemoteCommandsService::MetricReceivedRemoteCommand metric) const { + const char* metric_name = GetMetricNameReceivedRemoteCommand(scope_); + base::UmaHistogramEnumeration(metric_name, metric); +} + +void RemoteCommandsService::RecordExecutedRemoteCommand( + const RemoteCommandJob& command) const { + const std::string metric_name = + GetMetricNameExecutedRemoteCommand(scope_, command.GetType()); + base::UmaHistogramEnumeration(metric_name, command.status(), + RemoteCommandJob::STATUS_TYPE_SIZE); +} + +} // namespace policy |