blob: 2628539c85d10f5fa04bc402aee62982be7c6319 [file] [log] [blame]
// Copyright 2016 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 "authpolicy/authpolicy.h"
#include <unordered_set>
#include <utility>
#include <vector>
#include <base/strings/string_util.h>
#include <base/strings/stringprintf.h>
#include <base/threading/thread_task_runner_handle.h>
#include <dbus/authpolicy/dbus-constants.h>
#include <login_manager/proto_bindings/policy_descriptor.pb.h>
#include "authpolicy/authpolicy_metrics.h"
#include "authpolicy/cryptohome_client.h"
#include "authpolicy/log_colors.h"
#include "authpolicy/path_service.h"
#include "authpolicy/proto_bindings/active_directory_info.pb.h"
#include "authpolicy/samba_helper.h"
#include "authpolicy/samba_interface.h"
#include "authpolicy/session_manager_client.h"
#include "bindings/device_management_backend.pb.h"
namespace em = enterprise_management;
using brillo::dbus_utils::DBusObject;
using login_manager::PolicyDescriptor;
namespace authpolicy {
constexpr char kChromeUserPolicyType[] = "google/chromeos/user";
constexpr char kChromeDevicePolicyType[] = "google/chromeos/device";
constexpr char kChromeExtensionPolicyType[] = "google/chrome/extension";
namespace {
// Returns true if the given |domain| is expected to be associated with a
// component id in PolicyDescriptor, e.g. an extension id for
// POLICY_DOMAIN_EXTENSIONS.
bool DomainRequiresComponentId(login_manager::PolicyDomain domain) {
switch (domain) {
case login_manager::POLICY_DOMAIN_CHROME:
return false;
case login_manager::POLICY_DOMAIN_EXTENSIONS:
case login_manager::POLICY_DOMAIN_SIGNIN_EXTENSIONS:
// The component id is the extension id.
return true;
}
NOTREACHED() << "Invalid domain";
}
void PrintResult(const char* msg, ErrorType error) {
if (error == ERROR_NONE) {
LOG(INFO) << kColorRequestSuccess << msg << " succeeded" << kColorReset;
} else {
LOG(INFO) << kColorRequestFail << msg << " failed with code " << error
<< kColorReset;
}
}
ErrorMetricType GetPolicyErrorMetricType(bool is_refresh_user_policy) {
return is_refresh_user_policy ? ERROR_OF_REFRESH_USER_POLICY
: ERROR_OF_REFRESH_DEVICE_POLICY;
}
// Serializes |proto| to a vector of bytes. CHECKs for success (should
// never fail if there are no required proto fields).
std::vector<uint8_t> SerializeProto(
const google::protobuf::MessageLite& proto) {
std::vector<uint8_t> proto_blob(proto.ByteSizeLong());
CHECK(proto.SerializeToArray(proto_blob.data(), proto_blob.size()));
return proto_blob;
}
WARN_UNUSED_RESULT ErrorType
ParseProto(google::protobuf::MessageLite* proto,
const std::vector<uint8_t>& proto_blob) {
if (!proto->ParseFromArray(proto_blob.data(), proto_blob.size())) {
LOG(ERROR) << "Failed to parse proto";
return ERROR_PARSE_FAILED;
}
return ERROR_NONE;
}
} // namespace
// Tracks responses from D-Bus calls to Session Manager's StorePolicy during a
// Refresh*Policy call to AuthPolicy. StorePolicy is called N + 1 times (once
// for the main user/device policy and N times for extension policies, once per
// extension). The Refresh*Policy response callback is only called after all
// StorePolicy responses have been received. This class counts the responses and
// calls the Refresh*Policy response callback after the last response has been
// received. For tracking purposes, a failure to call StorePolicy (e.g. since
// parameters failed to serialize) counts as received response.
class ResponseTracker : public base::RefCountedThreadSafe<ResponseTracker> {
public:
ResponseTracker(bool is_refresh_user_policy,
int total_response_count,
AuthPolicyMetrics* metrics,
std::unique_ptr<ScopedTimerReporter> timer,
AuthPolicy::PolicyResponseCallback callback)
: is_refresh_user_policy_(is_refresh_user_policy),
outstanding_response_count_(total_response_count),
metrics_(metrics),
timer_(std::move(timer)),
callback_(std::move(callback)) {}
// Should be called when a response finished either successfully or not or if
// the corresponding StorePolicy call was never made, e.g. due to an error on
// call parameter setup. If |error_message| is empty, assumes that the
// StorePolicy call succeeded.
void OnResponseFinished(bool success) {
if (!success)
all_responses_succeeded_ = false;
// Don't use DCHECK here since bad policy store call counting could have
// security implications.
CHECK_GT(outstanding_response_count_, 0);
if (--outstanding_response_count_ == 0) {
// This is the last response, call the callback.
const ErrorMetricType metric_type =
GetPolicyErrorMetricType(is_refresh_user_policy_);
ErrorType error =
all_responses_succeeded_ ? ERROR_NONE : ERROR_STORE_POLICY_FAILED;
metrics_->ReportError(metric_type, error);
callback_->Return(error);
const char* request =
is_refresh_user_policy_ ? "RefreshUserPolicy" : "RefreshDevicePolicy";
PrintResult(request, error);
// Destroy the timer, which triggers the metric. It's going to be
// destroyed with this instance, anyway, but doing it here explicitly is
// easier to follow.
timer_.reset();
}
}
private:
bool is_refresh_user_policy_;
int outstanding_response_count_;
AuthPolicyMetrics* metrics_; // Not owned.
std::unique_ptr<ScopedTimerReporter> timer_;
AuthPolicy::PolicyResponseCallback callback_;
bool all_responses_succeeded_ = true;
};
// static
std::unique_ptr<DBusObject> AuthPolicy::GetDBusObject(
brillo::dbus_utils::ExportedObjectManager* object_manager) {
return std::make_unique<DBusObject>(
object_manager, object_manager->GetBus(),
org::chromium::AuthPolicyAdaptor::GetObjectPath());
}
AuthPolicy::AuthPolicy(AuthPolicyMetrics* metrics,
const PathService* path_service)
: org::chromium::AuthPolicyAdaptor(this),
metrics_(metrics),
samba_(metrics,
path_service,
base::Bind(&AuthPolicy::OnUserKerberosFilesChanged,
base::Unretained(this))) {}
AuthPolicy::~AuthPolicy() = default;
ErrorType AuthPolicy::Initialize(bool device_is_locked) {
device_is_locked_ = device_is_locked;
return samba_.Initialize(device_is_locked_ /* expect_config */);
}
void AuthPolicy::RegisterAsync(
std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object,
const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
completion_callback) {
DCHECK(!dbus_object_);
dbus_object_ = std::move(dbus_object);
// Make sure the task runner used in some places is actually the D-Bus task
// runner. This guarantees that tasks scheduled on the task runner won't
// interfere with D-Bus calls.
CHECK_EQ(base::ThreadTaskRunnerHandle::Get(),
dbus_object_->GetBus()->GetDBusTaskRunner());
RegisterWithDBusObject(dbus_object_.get());
dbus_object_->RegisterAsync(completion_callback);
session_manager_client_ =
std::make_unique<SessionManagerClient>(dbus_object_.get());
// Listen to session state changes for backing up user TGT and other data.
session_manager_client_->ConnectToSessionStateChangedSignal(base::Bind(
&SambaInterface::OnSessionStateChanged, base::Unretained(&samba_)));
// Set proper session state.
samba_.OnSessionStateChanged(session_manager_client_->RetrieveSessionState());
// Give Samba access to Cryptohome.
samba_.SetCryptohomeClient(
std::make_unique<CryptohomeClient>(dbus_object_.get()));
}
void AuthPolicy::AuthenticateUser(
AuthenticateUserResponseCallback callback,
const std::vector<uint8_t>& auth_user_request_blob,
const base::ScopedFD& password_fd) {
LOG(INFO) << kColorRequest << "Received 'AuthenticateUser' request"
<< kColorReset;
ScopedTimerReporter timer(TIMER_AUTHENTICATE_USER);
authpolicy::AuthenticateUserRequest request;
ErrorType error = ParseProto(&request, auth_user_request_blob);
ActiveDirectoryAccountInfo account_info;
if (error == ERROR_NONE) {
error = samba_.AuthenticateUser(request.user_principal_name(),
request.account_id(), password_fd.get(),
&account_info);
}
std::vector<uint8_t> account_info_blob;
if (error == ERROR_NONE)
account_info_blob = SerializeProto(account_info);
PrintResult("AuthenticateUser", error);
metrics_->ReportError(ERROR_OF_AUTHENTICATE_USER, error);
callback->Return(static_cast<int>(error), std::move(account_info_blob));
// Kick off the user affiliation check after responding, so that it can be
// done in parallel to Chrome startup. The affiliation flag is not needed
// until user policy fetch.
if (error == ERROR_NONE)
samba_.UpdateUserAffiliation();
}
void AuthPolicy::GetUserStatus(
const std::vector<uint8_t>& get_status_request_blob,
int32_t* int_error,
std::vector<uint8_t>* user_status_blob) {
LOG(INFO) << kColorRequest << "Received 'GetUserStatus' request"
<< kColorReset;
ScopedTimerReporter timer(TIMER_GET_USER_STATUS);
authpolicy::GetUserStatusRequest request;
ErrorType error = ParseProto(&request, get_status_request_blob);
ActiveDirectoryUserStatus user_status;
if (error == ERROR_NONE) {
error = samba_.GetUserStatus(request.user_principal_name(),
request.account_id(), &user_status);
}
if (error == ERROR_NONE)
*user_status_blob = SerializeProto(user_status);
PrintResult("GetUserStatus", error);
metrics_->ReportError(ERROR_OF_GET_USER_STATUS, error);
*int_error = static_cast<int>(error);
}
void AuthPolicy::GetUserKerberosFiles(
const std::string& account_id,
int32_t* int_error,
std::vector<uint8_t>* kerberos_files_blob) {
LOG(INFO) << kColorRequest << "Received 'GetUserKerberosFiles' request"
<< kColorReset;
ScopedTimerReporter timer(TIMER_GET_USER_KERBEROS_FILES);
KerberosFiles kerberos_files;
ErrorType error = samba_.GetUserKerberosFiles(account_id, &kerberos_files);
if (error == ERROR_NONE)
*kerberos_files_blob = SerializeProto(kerberos_files);
PrintResult("GetUserKerberosFiles", error);
metrics_->ReportError(ERROR_OF_GET_USER_KERBEROS_FILES, error);
*int_error = static_cast<int>(error);
}
void AuthPolicy::JoinADDomain(
const std::vector<uint8_t>& join_domain_request_blob,
const base::ScopedFD& password_fd,
int32_t* int_error,
std::string* joined_domain) {
LOG(INFO) << kColorRequest << "Received 'JoinADDomain' request"
<< kColorReset;
ScopedTimerReporter timer(TIMER_JOIN_AD_DOMAIN);
JoinDomainRequest request;
ErrorType error = ParseProto(&request, join_domain_request_blob);
if (error == ERROR_NONE) {
std::vector<std::string> machine_ou(request.machine_ou().begin(),
request.machine_ou().end());
error = samba_.JoinMachine(request.machine_name(), request.machine_domain(),
machine_ou, request.user_principal_name(),
request.kerberos_encryption_types(),
password_fd.get(), joined_domain);
}
PrintResult("JoinADDomain", error);
metrics_->ReportError(ERROR_OF_JOIN_AD_DOMAIN, error);
*int_error = static_cast<int>(error);
}
void AuthPolicy::RefreshUserPolicy(PolicyResponseCallback callback,
const std::string& account_id) {
LOG(INFO) << kColorRequest << "Received 'RefreshUserPolicy' request"
<< kColorReset;
auto timer = std::make_unique<ScopedTimerReporter>(TIMER_REFRESH_USER_POLICY);
// Fetch GPOs for the current user.
auto gpo_policy_data = std::make_unique<protos::GpoPolicyData>();
ErrorType error = samba_.FetchUserGpos(account_id, gpo_policy_data.get());
// Return immediately on error.
if (error != ERROR_NONE) {
PrintResult("RefreshUserPolicy", error);
metrics_->ReportError(ERROR_OF_REFRESH_USER_POLICY, error);
callback->Return(error);
return;
}
// Send policy to Session Manager.
const std::string account_id_key = GetAccountIdKey(account_id);
StorePolicy(std::move(gpo_policy_data), &account_id_key, std::move(timer),
std::move(callback));
}
void AuthPolicy::RefreshDevicePolicy(PolicyResponseCallback callback) {
LOG(INFO) << kColorRequest << "Received 'RefreshDevicePolicy' request"
<< kColorReset;
auto timer =
std::make_unique<ScopedTimerReporter>(TIMER_REFRESH_DEVICE_POLICY);
if (cached_device_policy_data_) {
// Send policy to Session Manager.
LOG(INFO) << "Using cached policy";
StorePolicy(std::move(cached_device_policy_data_), nullptr,
std::move(timer), std::move(callback));
return;
}
// Fetch GPOs for the device.
auto gpo_policy_data = std::make_unique<protos::GpoPolicyData>();
ErrorType error = samba_.FetchDeviceGpos(gpo_policy_data.get());
device_is_locked_ = device_is_locked_ || InstallAttributesReader().IsLocked();
if (!device_is_locked_ && error == ERROR_NONE) {
LOG(INFO) << "Device is not locked yet. Caching device policy.";
cached_device_policy_data_ = std::move(gpo_policy_data);
error = ERROR_DEVICE_POLICY_CACHED_BUT_NOT_SENT;
}
// Return immediately on error.
if (error != ERROR_NONE) {
PrintResult("RefreshDevicePolicy", error);
metrics_->ReportError(ERROR_OF_REFRESH_DEVICE_POLICY, error);
callback->Return(error);
return;
}
// Send policy to Session Manager.
StorePolicy(std::move(gpo_policy_data), nullptr, std::move(timer),
std::move(callback));
}
std::string AuthPolicy::SetDefaultLogLevel(int32_t level) {
LOG(INFO) << kColorRequest << "Received 'SetDefaultLogLevel' request"
<< kColorReset;
if (level < AuthPolicyFlags::kMinLevel ||
level > AuthPolicyFlags::kMaxLevel) {
std::string message = base::StringPrintf("Level must be between %i and %i.",
AuthPolicyFlags::kMinLevel,
AuthPolicyFlags::kMaxLevel);
LOG(ERROR) << message;
return message;
}
samba_.SetDefaultLogLevel(static_cast<AuthPolicyFlags::DefaultLevel>(level));
return std::string();
}
int32_t AuthPolicy::ChangeMachinePasswordForTesting() {
return samba_.ChangeMachinePasswordForTesting();
}
void AuthPolicy::OnUserKerberosFilesChanged() {
LOG(INFO) << "Firing signal UserKerberosFilesChanged";
SendUserKerberosFilesChangedSignal();
}
void AuthPolicy::StorePolicy(
std::unique_ptr<protos::GpoPolicyData> gpo_policy_data,
const std::string* account_id_key,
std::unique_ptr<ScopedTimerReporter> timer,
PolicyResponseCallback callback) {
// Build descriptor that specifies where the policy is stored.
PolicyDescriptor descriptor;
const bool is_refresh_user_policy = account_id_key != nullptr;
const char* policy_type = nullptr;
if (is_refresh_user_policy) {
DCHECK(!account_id_key->empty());
descriptor.set_account_type(login_manager::ACCOUNT_TYPE_USER);
descriptor.set_account_id(*account_id_key);
policy_type = kChromeUserPolicyType;
} else {
descriptor.set_account_type(login_manager::ACCOUNT_TYPE_DEVICE);
policy_type = kChromeDevicePolicyType;
}
// Query IDs of extension policy stored by Session Manager.
descriptor.set_domain(login_manager::POLICY_DOMAIN_EXTENSIONS);
std::vector<std::string> existing_extension_ids;
if (!session_manager_client_->ListStoredComponentPolicies(
SerializeProto(descriptor), &existing_extension_ids)) {
// If this call fails, worst thing that can happen is stale extension
// policy. Still seems better than not pushing policy at all, so keep going.
existing_extension_ids.clear();
LOG(WARNING) << "Cannot clean up stale extension policies: "
"Failed to get list of stored extension policies.";
}
// Extension policies that are no longer coming down from Active Directory
// have to be deleted. Those are (IDs in Session Manager) - (IDs from AD).
std::unordered_set<std::string> extension_ids_to_delete;
extension_ids_to_delete.insert(existing_extension_ids.begin(),
existing_extension_ids.end());
for (int n = 0; n < gpo_policy_data->extension_policies_size(); ++n)
extension_ids_to_delete.erase(gpo_policy_data->extension_policies(n).id());
// Count total number of StorePolicy responses we're expecting and create a
// tracker object that counts the number of outstanding responses and keeps
// some unique pointers.
const int num_extensions_to_store =
gpo_policy_data->extension_policies_size();
const int num_extensions_to_delete =
static_cast<int>(extension_ids_to_delete.size());
const int num_store_policy_calls =
1 + num_extensions_to_store + num_extensions_to_delete;
LOG(INFO) << "Sending " << (is_refresh_user_policy ? "user" : "device")
<< " policy to Session Manager (Chrome policy, "
<< num_extensions_to_store << " extensions). Deleting "
<< num_extensions_to_delete << " stale extensions.";
scoped_refptr<ResponseTracker> response_tracker =
new ResponseTracker(is_refresh_user_policy, num_store_policy_calls,
metrics_, std::move(timer), std::move(callback));
// For double checking we counted the number of store calls right.
int store_policy_call_count = 0;
// Store the user or device policy.
descriptor.set_domain(login_manager::POLICY_DOMAIN_CHROME);
StoreSinglePolicy(descriptor, policy_type,
&gpo_policy_data->user_or_device_policy(),
response_tracker);
store_policy_call_count++;
// Store extension policies.
descriptor.set_domain(login_manager::POLICY_DOMAIN_EXTENSIONS);
for (int n = 0; n < num_extensions_to_store; ++n) {
const protos::ExtensionPolicy& extension_policy =
gpo_policy_data->extension_policies(n);
descriptor.set_component_id(extension_policy.id());
StoreSinglePolicy(descriptor, kChromeExtensionPolicyType,
&extension_policy.json_data(), response_tracker);
store_policy_call_count++;
}
// Remove policies for extensions that are no longer coming down from AD.
descriptor.set_domain(login_manager::POLICY_DOMAIN_EXTENSIONS);
for (const std::string& extension_id : extension_ids_to_delete) {
descriptor.set_component_id(extension_id);
StoreSinglePolicy(descriptor, kChromeExtensionPolicyType,
nullptr /* policy_blob */, response_tracker);
store_policy_call_count++;
}
// Don't use DCHECK here since bad policy store call counting could have
// security implications.
CHECK(store_policy_call_count == num_store_policy_calls);
}
void AuthPolicy::StoreSinglePolicy(
const PolicyDescriptor& descriptor,
const char* policy_type,
const std::string* policy_blob,
scoped_refptr<ResponseTracker> response_tracker) {
// Sending an empty response_blob deletes the policy.
if (!policy_blob) {
session_manager_client_->StoreUnsignedPolicyEx(
SerializeProto(descriptor), std::vector<uint8_t>() /* response_blob */,
base::Bind(&ResponseTracker::OnResponseFinished, response_tracker));
return;
}
// Wrap up the policy in a PolicyFetchResponse.
em::PolicyData policy_data;
policy_data.set_policy_value(*policy_blob);
policy_data.set_policy_type(policy_type);
if (descriptor.account_type() == login_manager::ACCOUNT_TYPE_USER) {
policy_data.set_username(samba_.GetUserPrincipal());
// Device id in the proto also could be used as an account/client id.
policy_data.set_device_id(samba_.user_account_id());
if (samba_.is_user_affiliated())
policy_data.add_user_affiliation_ids(kAffiliationMarker);
} else {
DCHECK(descriptor.account_type() == login_manager::ACCOUNT_TYPE_DEVICE);
policy_data.set_device_id(samba_.machine_name());
policy_data.add_device_affiliation_ids(kAffiliationMarker);
}
// TODO(crbug.com/831995): Use timer that can never run backwards and enable
// timestamp validation in the Chromium Active Directory policy manager.
policy_data.set_timestamp(base::Time::Now().ToJavaTime());
policy_data.set_management_mode(em::PolicyData::ENTERPRISE_MANAGED);
policy_data.set_machine_name(samba_.machine_name());
if (DomainRequiresComponentId(descriptor.domain())) {
DCHECK(!descriptor.component_id().empty());
policy_data.set_settings_entity_id(descriptor.component_id());
}
// Note: No signature required here, Active Directory policy is unsigned!
em::PolicyFetchResponse policy_response;
if (!policy_data.SerializeToString(policy_response.mutable_policy_data())) {
LOG(ERROR) << "Failed to serialize policy data";
response_tracker->OnResponseFinished(false);
return;
}
session_manager_client_->StoreUnsignedPolicyEx(
SerializeProto(descriptor), SerializeProto(policy_response),
base::Bind(&ResponseTracker::OnResponseFinished, response_tracker));
}
} // namespace authpolicy