Skip to content

Commit

Permalink
Remote powerwash on Chromad via policy
Browse files Browse the repository at this point in the history
Implement remote powerwash on Chromad, using the recently added
ChromadToCloudMigrationEnabled policy. The powerwash will be used to
start the migration of AD managed devices into cloud management. Besides
having the new policy enabled, the device needs to be on the login
screen and the enrollment ID must have already been uploaded to
DMServer.

Bug: 1209246
Change-Id: I6a67b8a8a43c28bc5f03f27e96d0415f69b6bc83
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3141873
Reviewed-by: Maksim Ivanov <emaxx@chromium.org>
Reviewed-by: Roman Sorokin <rsorokin@chromium.org>
Reviewed-by: Colin Blundell <blundell@chromium.org>
Commit-Queue: Felipe Andrade <fsandrade@chromium.org>
Cr-Commit-Position: refs/heads/main@{#959123}
  • Loading branch information
Felipe Andrade authored and Chromium LUCI CQ committed Jan 14, 2022
1 parent 2c36908 commit 6ff1599
Show file tree
Hide file tree
Showing 12 changed files with 668 additions and 6 deletions.
2 changes: 1 addition & 1 deletion chrome/browser/ash/kerberos/kerberos_credentials_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ class KerberosCredentialsManager : public KeyedService,
void ListAccounts(ListAccountsCallback callback);

// Sets the contents of the Kerberos configuration (krb5.conf) to |krb5_conf|
// for the account with given |principal_name|.
// for the account with given |principal_name|.
void SetConfig(std::string principal_name,
const std::string& krb5_conf,
ResultCallback callback);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// Copyright 2022 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/ash/policy/active_directory/active_directory_migration_manager.h"

#include <utility>

#include "ash/constants/ash_pref_names.h"
#include "base/bind.h"
#include "base/location.h"
#include "base/time/time.h"
#include "chrome/common/pref_names.h"
#include "chromeos/dbus/session_manager/session_manager_client.h"
#include "components/policy/proto/device_management_backend.pb.h"
#include "components/prefs/pref_change_registrar.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/session_manager/core/session_manager.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"

namespace em = enterprise_management;

namespace policy {

namespace {

// The amount of time we wait before actively checking the preconditions to
// start the migration again.
constexpr base::TimeDelta kRetryDelay = base::Hours(1);

// The amount of time we wait before triggering a new powerwash, in case the
// last request has failed for any reason.
constexpr base::TimeDelta kPowerwashBackoffTime = base::Days(1);

// Returns true if any user is logged in (session is started).
bool IsUserLoggedIn() {
auto* session_manager = session_manager::SessionManager::Get();
return session_manager && session_manager->IsSessionStarted();
}

} // namespace

ActiveDirectoryMigrationManager::ActiveDirectoryMigrationManager(
PrefService* local_state)
: local_state_(local_state) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
DCHECK(local_state_);

// Listen to user session state changes.
auto* session_manager = session_manager::SessionManager::Get();
if (session_manager) {
session_manager->AddObserver(this);
}
}

ActiveDirectoryMigrationManager::~ActiveDirectoryMigrationManager() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

// Stop listening to user session state changes.
auto* session_manager = session_manager::SessionManager::Get();
if (session_manager) {
session_manager->RemoveObserver(this);
}
}

// static
void ActiveDirectoryMigrationManager::RegisterLocalStatePrefs(
PrefRegistrySimple* registry) {
registry->RegisterTimePref(prefs::kLastChromadMigrationAttemptTime,
/*default_value=*/base::Time());
}

void ActiveDirectoryMigrationManager::Init() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

// Listen to pref changes.
pref_change_registrar_ = std::make_unique<PrefChangeRegistrar>();
pref_change_registrar_->Init(local_state_);
pref_change_registrar_->Add(
prefs::kEnrollmentIdUploadedOnChromad,
base::BindRepeating(
&ActiveDirectoryMigrationManager::OnEnrollmentIdUploadedPrefChanged,
weak_ptr_factory_.GetWeakPtr()));
pref_change_registrar_->Add(
ash::prefs::kChromadToCloudMigrationEnabled,
base::BindRepeating(&ActiveDirectoryMigrationManager::
OnChromadMigrationEnabledPrefChanged,
weak_ptr_factory_.GetWeakPtr()));

// Check the conditions here as well, because this manager might be
// initialized while the pre-conditions are already satisfied.
TryToStartMigration();
}

void ActiveDirectoryMigrationManager::SetStatusCallbackForTesting(
StatusCallback callback) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

status_callback_for_testing_ = std::move(callback);
}

bool ActiveDirectoryMigrationManager::HasUploadedEnrollmentId() const {
return local_state_->GetBoolean(prefs::kEnrollmentIdUploadedOnChromad);
}

bool ActiveDirectoryMigrationManager::IsChromadMigrationEnabled() const {
return local_state_->GetBoolean(ash::prefs::kChromadToCloudMigrationEnabled);
}

bool ActiveDirectoryMigrationManager::HasBackoffTimePassed() const {
base::Time last_migration_attempt_time =
local_state_->GetTime(prefs::kLastChromadMigrationAttemptTime);
base::Time now = base::Time::Now();

return now - last_migration_attempt_time > kPowerwashBackoffTime;
}

void ActiveDirectoryMigrationManager::OnEnrollmentIdUploadedPrefChanged() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

TryToStartMigration();
}

void ActiveDirectoryMigrationManager::OnChromadMigrationEnabledPrefChanged() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

TryToStartMigration();
}

void ActiveDirectoryMigrationManager::OnLoginOrLockScreenVisible() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

TryToStartMigration();
}

void ActiveDirectoryMigrationManager::TryToStartMigration() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

bool is_on_login_screen = !IsUserLoggedIn();

if (is_on_login_screen && HasUploadedEnrollmentId() &&
IsChromadMigrationEnabled() && HasBackoffTimePassed()) {
StartPowerwash();
MaybeRunStatusCallback(/*started=*/true, /*rescheduled=*/false);
return;
}

// Theoretically, the following reschedule logic is not necessary. However, it
// was added as a fallback, in case any of the signals this class listens is
// not triggered as expected. Ultimatelly, we want to avoid inactive devices
// getting stuck and not migrating.
if (is_on_login_screen && !retry_already_scheduled_) {
retry_already_scheduled_ = true;
content::GetUIThreadTaskRunner({})->PostDelayedTask(
FROM_HERE,
base::BindOnce(&ActiveDirectoryMigrationManager::RetryToStartMigration,
weak_ptr_factory_.GetWeakPtr()),
kRetryDelay);

MaybeRunStatusCallback(/*started=*/false, /*rescheduled=*/true);
return;
}

MaybeRunStatusCallback(/*started=*/false, /*rescheduled=*/false);
}

void ActiveDirectoryMigrationManager::RetryToStartMigration() {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

retry_already_scheduled_ = false;
TryToStartMigration();
}

void ActiveDirectoryMigrationManager::StartPowerwash() {
local_state_->SetTime(prefs::kLastChromadMigrationAttemptTime,
base::Time::Now());

// Unsigned remote powerwash requests are allowed in AD mode.
chromeos::SessionManagerClient::Get()->StartRemoteDeviceWipe(
em::SignedData());
}

void ActiveDirectoryMigrationManager::MaybeRunStatusCallback(bool started,
bool rescheduled) {
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);

if (status_callback_for_testing_) {
std::move(status_callback_for_testing_).Run(started, rescheduled);
}
}

} // namespace policy
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Copyright 2022 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_ASH_POLICY_ACTIVE_DIRECTORY_ACTIVE_DIRECTORY_MIGRATION_MANAGER_H_
#define CHROME_BROWSER_ASH_POLICY_ACTIVE_DIRECTORY_ACTIVE_DIRECTORY_MIGRATION_MANAGER_H_

#include <memory>

#include "base/callback.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/weak_ptr.h"
#include "components/session_manager/core/session_manager_observer.h"

class PrefChangeRegistrar;
class PrefRegistrySimple;
class PrefService;

namespace policy {

// Manages the migration of AD managed devices into cloud management. The goal
// is to start the migration when (a) the device is on the login screen, (b) the
// enrollment ID has already been uploaded to DMServer and (c) the
// `ChromadToCloudMigrationEnabled` policy is enabled. After being constructed,
// this class listens to changes from the `SessionManager` and from the
// `kEnrollmentIdUploadedOnChromad` and `kChromadToCloudMigrationEnabled` local
// state prefs. Additionally, these checks are periodically executed while the
// device is on the login screen.
class ActiveDirectoryMigrationManager
: public session_manager::SessionManagerObserver {
public:
explicit ActiveDirectoryMigrationManager(PrefService* local_state);

~ActiveDirectoryMigrationManager() override;

// Disallow copy and assignment.
ActiveDirectoryMigrationManager(const ActiveDirectoryMigrationManager&) =
delete;
ActiveDirectoryMigrationManager& operator=(
const ActiveDirectoryMigrationManager&) = delete;

static void RegisterLocalStatePrefs(PrefRegistrySimple* registry);

// Registers to prefs change and tries to start the migration.
void Init();

// Callback called when the `TryToStartMigration` method is executed. Returns
// whether the migrations started or not, and whether a retry was scheduled or
// not.
using StatusCallback =
base::OnceCallback<void(bool started, bool rescheduled)>;

// Only used for testing.
void SetStatusCallbackForTesting(StatusCallback callback);

private:
// Returns true if the enrollment ID has already been uploaded.
bool HasUploadedEnrollmentId() const;

// Returns true if the migration of Chromad devices to cloud management is
// enabled.
bool IsChromadMigrationEnabled() const;

// Returns true if the last powerwash attempt happened more than
// `kPowerwashBackoffTime` ago.
bool HasBackoffTimePassed() const;

// Pref change handlers.
void OnEnrollmentIdUploadedPrefChanged();
void OnChromadMigrationEnabledPrefChanged();

// session_manager::SessionManagerObserver:
void OnLoginOrLockScreenVisible() override;

// Triggers a device powerwash, if the pre-requisites are satisfied. Called
// every time one of the three events of interest happens. Also called
// periodically while the device is on the login screen.
void TryToStartMigration();

// Executes the same steps as `TryToStartMigration`, but also updates the
// value of `retry_already_scheduled_` accordingly.
void RetryToStartMigration();

// Sends a device powerwash request through D-Bus.
void StartPowerwash();

// Runs the `status_callback_for_testing_`, if it's not empty. Passes the
// received boolean values to the callback. Only used for testing.
void MaybeRunStatusCallback(bool started, bool rescheduled);

// Local state prefs, not owned.
raw_ptr<PrefService> local_state_;

// Observer for Chromad migration related prefs.
std::unique_ptr<PrefChangeRegistrar> pref_change_registrar_;

bool retry_already_scheduled_ = false;

StatusCallback status_callback_for_testing_;

// Must be the last member.
base::WeakPtrFactory<ActiveDirectoryMigrationManager> weak_ptr_factory_{this};
};

} // namespace policy

#endif // CHROME_BROWSER_ASH_POLICY_ACTIVE_DIRECTORY_ACTIVE_DIRECTORY_MIGRATION_MANAGER_H_
Loading

0 comments on commit 6ff1599

Please sign in to comment.