Commit 9d9a2af4 authored by Owen Min's avatar Owen Min Committed by Commit Bot

Create MachineLevelUserCloudPolicyStore

This is a new implementation of DesktopCloudPolicyStore. It has
- Different path of policy cache
  The policy files are store as
    "$user_data_dir/Policy/Machine Level User Cloud Policy" and
     "$user_data_dir/Policy/Machine Level User Cloud Policy Signing Key".
- Global dm_token and client_id
  It will load the policy cache from disk iff the global token is existed.
  And it will validate the dm_token and client_id with the global ones after
  store/load.

Bug: 812641
Change-Id: If02ad77388fc197f20dc25c04c1f852e1827253a
Reviewed-on: https://chromium-review.googlesource.com/940442
Commit-Queue: Owen Min <zmin@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Cr-Commit-Position: refs/heads/master@{#541582}
parent 2446e013
......@@ -62,6 +62,8 @@ source_set("internal") {
"cloud/external_policy_data_fetcher.h",
"cloud/external_policy_data_updater.cc",
"cloud/external_policy_data_updater.h",
"cloud/machine_level_user_cloud_policy_store.cc",
"cloud/machine_level_user_cloud_policy_store.h",
"cloud/policy_header_io_helper.cc",
"cloud/policy_header_io_helper.h",
"cloud/policy_header_service.cc",
......@@ -200,6 +202,8 @@ source_set("internal") {
sources -= [
"cloud/cloud_policy_client_registration_helper.cc",
"cloud/cloud_policy_client_registration_helper.h",
"cloud/machine_level_user_cloud_policy_store.cc",
"cloud/machine_level_user_cloud_policy_store.h",
"cloud/user_cloud_policy_manager.cc",
"cloud/user_cloud_policy_manager.h",
"cloud/user_cloud_policy_store.cc",
......@@ -318,6 +322,7 @@ source_set("unit_tests") {
]
} else {
sources += [
"cloud/machine_level_user_cloud_policy_store_unittest.cc",
"cloud/user_cloud_policy_manager_unittest.cc",
"cloud/user_cloud_policy_store_unittest.cc",
]
......
// Copyright 2018 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/cloud/machine_level_user_cloud_policy_store.h"
#include <utility>
#include "base/memory/ptr_util.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
namespace policy {
namespace {
const base::FilePath::CharType kPolicyDir[] = FILE_PATH_LITERAL("Policy");
const base::FilePath::CharType kPolicyCache[] =
FILE_PATH_LITERAL("Machine Level User Cloud Policy");
const base::FilePath::CharType kKeyCache[] =
FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key");
} // namespace
MachineLevelUserCloudPolicyStore::MachineLevelUserCloudPolicyStore(
const std::string& machine_dm_token,
const std::string& machine_client_id,
const base::FilePath& policy_path,
const base::FilePath& key_path,
scoped_refptr<base::SequencedTaskRunner> background_task_runner)
: DesktopCloudPolicyStore(policy_path,
key_path,
background_task_runner,
PolicyScope::POLICY_SCOPE_MACHINE),
machine_dm_token_(machine_dm_token),
machine_client_id_(machine_client_id) {}
MachineLevelUserCloudPolicyStore::~MachineLevelUserCloudPolicyStore() {}
// static
std::unique_ptr<MachineLevelUserCloudPolicyStore>
MachineLevelUserCloudPolicyStore::Create(
const std::string& machine_dm_token,
const std::string& machine_client_id,
const base::FilePath& user_data_dir,
scoped_refptr<base::SequencedTaskRunner> background_task_runner) {
base::FilePath policy_dir = user_data_dir.Append(kPolicyDir);
base::FilePath policy_cache_file = policy_dir.Append(kPolicyCache);
base::FilePath key_cache_file = policy_dir.Append(kKeyCache);
return std::make_unique<MachineLevelUserCloudPolicyStore>(
machine_dm_token, machine_client_id, policy_cache_file, key_cache_file,
background_task_runner);
}
void MachineLevelUserCloudPolicyStore::LoadImmediately() {
// There is no global dm token, stop loading the policy cache. The policy will
// be fetched in the end of enrollment process.
if (machine_dm_token_.empty()) {
DVLOG(1) << "LoadImmediately ignored, no DM token";
return;
}
DesktopCloudPolicyStore::LoadImmediately();
}
void MachineLevelUserCloudPolicyStore::Load() {
// There is no global dm token, stop loading the policy cache. The policy will
// be fetched in the end of enrollment process.
if (machine_dm_token_.empty()) {
DVLOG(1) << "Load ignored, no DM token";
return;
}
DesktopCloudPolicyStore::Load();
}
std::unique_ptr<UserCloudPolicyValidator>
MachineLevelUserCloudPolicyStore::CreateValidator(
std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
CloudPolicyValidatorBase::ValidateTimestampOption option) {
std::unique_ptr<UserCloudPolicyValidator> validator =
UserCloudPolicyValidator::Create(std::move(policy),
background_task_runner());
validator->ValidatePolicyType(
dm_protocol::kChromeMachineLevelUserCloudPolicyType);
validator->ValidateDMToken(machine_dm_token_,
CloudPolicyValidatorBase::DM_TOKEN_REQUIRED);
validator->ValidateDeviceId(machine_client_id_,
CloudPolicyValidatorBase::DEVICE_ID_REQUIRED);
if (policy_) {
validator->ValidateTimestamp(base::Time::FromJavaTime(policy_->timestamp()),
option);
}
validator->ValidatePayload();
return validator;
}
void MachineLevelUserCloudPolicyStore::SetupRegistration(
const std::string& machine_dm_token,
const std::string& machine_client_id) {
machine_dm_token_ = machine_dm_token;
machine_client_id_ = machine_client_id;
}
void MachineLevelUserCloudPolicyStore::Validate(
std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
std::unique_ptr<enterprise_management::PolicySigningKey> key,
bool validate_in_background,
const UserCloudPolicyValidator::CompletionCallback& callback) {
std::unique_ptr<UserCloudPolicyValidator> validator = CreateValidator(
std::move(policy), CloudPolicyValidatorBase::TIMESTAMP_VALIDATED);
ValidateKeyAndSignature(validator.get(), key.get(), std::string());
if (validate_in_background) {
UserCloudPolicyValidator::StartValidation(std::move(validator), callback);
} else {
validator->RunValidation();
callback.Run(validator.get());
}
}
} // namespace policy
// Copyright 2018 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 COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
#define COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
#include <memory>
#include <string>
#include "base/macros.h"
#include "components/policy/core/common/cloud/user_cloud_policy_store.h"
namespace policy {
// Implements a cloud policy store that stores policy for machine level user
// cloud policy. This is used on (non-chromeos) platforms that do no have a
// secure storage implementation.
class POLICY_EXPORT MachineLevelUserCloudPolicyStore
: public DesktopCloudPolicyStore {
public:
MachineLevelUserCloudPolicyStore(
const std::string& machine_dm_token,
const std::string& machine_client_id,
const base::FilePath& policy_path,
const base::FilePath& key_path,
scoped_refptr<base::SequencedTaskRunner> background_task_runner);
~MachineLevelUserCloudPolicyStore() override;
static std::unique_ptr<MachineLevelUserCloudPolicyStore> Create(
const std::string& machine_dm_token,
const std::string& machine_client_id,
const base::FilePath& user_data_dir,
scoped_refptr<base::SequencedTaskRunner> background_task_runner);
// override DesktopCloudPolicyStore
void LoadImmediately() override;
void Load() override;
// override UserCloudPolicyStoreBase
std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
CloudPolicyValidatorBase::ValidateTimestampOption option) override;
// Setup global |dm_token| and |client_id| in store for the validation purpose
// before policy refresh.
void SetupRegistration(const std::string& machine_dm_token,
const std::string& machine_client_id);
private:
// override DesktopCloudPolicyStore
void Validate(
std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
std::unique_ptr<enterprise_management::PolicySigningKey> key,
bool validate_in_background,
const UserCloudPolicyValidator::CompletionCallback& callback) override;
std::string machine_dm_token_;
std::string machine_client_id_;
DISALLOW_COPY_AND_ASSIGN(MachineLevelUserCloudPolicyStore);
};
} // namespace policy
#endif // COMPONENTS_POLICY_CORE_COMMON_CLOUD_MACHINE_LEVEL_USER_CLOUD_POLICY_STORE_H_
// Copyright 2018 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/cloud/machine_level_user_cloud_policy_store.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/policy/core/common/cloud/mock_cloud_policy_store.h"
#include "components/policy/core/common/cloud/policy_builder.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using ::testing::_;
namespace policy {
// The unit test for MachineLevelUserCloudPolicyStore. Note that most of test
// cases are covered by UserCloudPolicyStoreTest so that the cases here are
// focus on testing the unique part of MachineLevelUserCloudPolicyStore.
class MachineLevelUserCloudPolicyStoreTest : public ::testing::Test {
public:
MachineLevelUserCloudPolicyStoreTest()
: scoped_task_environment_(
base::test::ScopedTaskEnvironment::MainThreadType::UI) {
policy_.SetDefaultInitialSigningKey();
policy_.policy_data().set_policy_type(
dm_protocol::kChromeMachineLevelUserCloudPolicyType);
policy_.payload().mutable_incognitoenabled()->set_value(false);
policy_.Build();
}
~MachineLevelUserCloudPolicyStoreTest() override {}
void SetUp() override {
ASSERT_TRUE(tmp_user_data_dir_.CreateUniqueTempDir());
store_ = CreateStore();
}
std::unique_ptr<MachineLevelUserCloudPolicyStore> CreateStore() {
std::unique_ptr<MachineLevelUserCloudPolicyStore> store =
MachineLevelUserCloudPolicyStore::Create(
PolicyBuilder::kFakeToken, PolicyBuilder::kFakeDeviceId,
tmp_user_data_dir_.GetPath(), base::ThreadTaskRunnerHandle::Get());
store->AddObserver(&observer_);
return store;
}
void TearDown() override {
store_->RemoveObserver(&observer_);
store_.reset();
base::RunLoop().RunUntilIdle();
}
std::unique_ptr<MachineLevelUserCloudPolicyStore> store_;
base::ScopedTempDir tmp_user_data_dir_;
UserPolicyBuilder policy_;
MockCloudPolicyStoreObserver observer_;
private:
base::test::ScopedTaskEnvironment scoped_task_environment_;
DISALLOW_COPY_AND_ASSIGN(MachineLevelUserCloudPolicyStoreTest);
};
TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadWithoutDMToken) {
store_->SetupRegistration(std::string(), std::string());
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_CALL(observer_, OnStoreLoaded(_)).Times(0);
EXPECT_CALL(observer_, OnStoreError(_)).Times(0);
store_->Load();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadImmediatelyWithoutDMToken) {
store_->SetupRegistration(std::string(), std::string());
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_CALL(observer_, OnStoreLoaded(_)).Times(0);
EXPECT_CALL(observer_, OnStoreError(_)).Times(0);
store_->LoadImmediately();
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest, LoadWithNoFile) {
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Load();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest, StorePolicy) {
EXPECT_FALSE(store_->policy());
EXPECT_TRUE(store_->policy_map().empty());
const base::FilePath policy_path =
tmp_user_data_dir_.GetPath()
.Append(FILE_PATH_LITERAL("Policy"))
.Append(FILE_PATH_LITERAL("Machine Level User Cloud Policy"));
const base::FilePath signing_key_path =
tmp_user_data_dir_.GetPath()
.Append(FILE_PATH_LITERAL("Policy"))
.Append(
FILE_PATH_LITERAL("Machine Level User Cloud Policy Signing Key"));
EXPECT_FALSE(base::PathExists(policy_path));
EXPECT_FALSE(base::PathExists(signing_key_path));
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Store(policy_.policy());
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(store_->policy());
EXPECT_EQ(policy_.policy_data().SerializeAsString(),
store_->policy()->SerializeAsString());
EXPECT_EQ(CloudPolicyStore::STATUS_OK, store_->status());
EXPECT_TRUE(base::PathExists(policy_path));
EXPECT_TRUE(base::PathExists(signing_key_path));
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest, StoreThenLoadPolicy) {
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Store(policy_.policy());
base::RunLoop().RunUntilIdle();
::testing::Mock::VerifyAndClearExpectations(&observer_);
std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
EXPECT_CALL(observer_, OnStoreLoaded(loader.get()));
loader->Load();
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(loader->policy());
EXPECT_EQ(policy_.policy_data().SerializeAsString(),
loader->policy()->SerializeAsString());
EXPECT_EQ(CloudPolicyStore::STATUS_OK, loader->status());
loader->RemoveObserver(&observer_);
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest,
StoreAndLoadWithInvalidTokenPolicy) {
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Store(policy_.policy());
base::RunLoop().RunUntilIdle();
::testing::Mock::VerifyAndClearExpectations(&observer_);
std::unique_ptr<MachineLevelUserCloudPolicyStore> loader = CreateStore();
loader->SetupRegistration("invalid_token", "invalid_client_id");
EXPECT_CALL(observer_, OnStoreError(loader.get()));
loader->Load();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(loader->policy());
EXPECT_TRUE(loader->policy_map().empty());
EXPECT_EQ(CloudPolicyStore::STATUS_VALIDATION_ERROR, loader->status());
loader->RemoveObserver(&observer_);
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
TEST_F(MachineLevelUserCloudPolicyStoreTest, KeyRotation) {
EXPECT_FALSE(policy_.policy().has_new_public_key_signature());
std::string original_policy_key = policy_.policy().new_public_key();
// Store the original key
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Store(policy_.policy());
base::RunLoop().RunUntilIdle();
ASSERT_TRUE(store_->policy());
EXPECT_EQ(original_policy_key, store_->policy_signature_public_key());
::testing::Mock::VerifyAndClearExpectations(&observer_);
// Store the new key.
policy_.SetDefaultSigningKey();
policy_.SetDefaultNewSigningKey();
policy_.Build();
EXPECT_TRUE(policy_.policy().has_new_public_key_signature());
EXPECT_NE(original_policy_key, policy_.policy().new_public_key());
EXPECT_CALL(observer_, OnStoreLoaded(store_.get()));
store_->Store(policy_.policy());
base::RunLoop().RunUntilIdle();
EXPECT_EQ(policy_.policy().new_public_key(),
store_->policy_signature_public_key());
::testing::Mock::VerifyAndClearExpectations(&observer_);
}
} // namespace policy
......@@ -267,6 +267,60 @@ void DesktopCloudPolicyStore::PolicyLoaded(bool validate_in_background,
}
}
void DesktopCloudPolicyStore::ValidateKeyAndSignature(
UserCloudPolicyValidator* validator,
const em::PolicySigningKey* cached_key,
const std::string& owning_domain) {
// There are 4 cases:
//
// 1) Validation after loading from cache with no cached key.
// Action: Just validate signature with an empty key - this will result in
// a failed validation and the cached policy will be rejected.
//
// 2) Validation after loading from cache with a cached key
// Action: Validate signature on policy blob but don't allow key rotation.
//
// 3) Validation after loading new policy from the server with no cached key
// Action: Validate as initial key provisioning (case where we are migrating
// from unsigned policy)
//
// 4) Validation after loading new policy from the server with a cached key
// Action: Validate as normal, and allow key rotation.
if (cached_key) {
// Case #1/#2 - loading from cache. Validate the cached key (if no key,
// then the validation will fail), then do normal policy data signature
// validation using the cached key.
// Loading from cache should not change the cached keys.
DCHECK(persisted_policy_key_.empty() ||
persisted_policy_key_ == cached_key->signing_key());
DLOG_IF(WARNING, !cached_key->has_signing_key())
<< "Unsigned policy blob detected";
validator->ValidateCachedKey(cached_key->signing_key(),
cached_key->signing_key_signature(),
owning_domain);
// Loading from cache, so don't allow key rotation.
validator->ValidateSignature(cached_key->signing_key());
} else {
// No passed cached_key - this is not validating the initial policy load
// from cache, but rather an update from the server.
if (persisted_policy_key_.empty()) {
// Case #3 - no valid existing policy key (either this is the initial
// policy fetch, or we're doing a key rotation), so this new policy fetch
// should include an initial key provision.
validator->ValidateInitialKey(owning_domain);
} else {
// Case #4 - verify new policy with existing key. We always allow key
// rotation - the verification key will prevent invalid policy from being
// injected. |persisted_policy_key_| is already known to be valid, so no
// need to verify via ValidateCachedKey().
validator->ValidateSignatureAllowingRotation(persisted_policy_key_,
owning_domain);
}
}
}
void DesktopCloudPolicyStore::InstallLoadedPolicyAfterValidation(
bool doing_key_rotation,
const std::string& signing_key,
......@@ -399,54 +453,7 @@ void UserCloudPolicyStore::Validate(
gaia::CanonicalizeEmail(gaia::SanitizeEmail(signin_username_)));
}
// There are 4 cases:
//
// 1) Validation after loading from cache with no cached key.
// Action: Just validate signature with an empty key - this will result in
// a failed validation and the cached policy will be rejected.
//
// 2) Validation after loading from cache with a cached key
// Action: Validate signature on policy blob but don't allow key rotation.
//
// 3) Validation after loading new policy from the server with no cached key
// Action: Validate as initial key provisioning (case where we are migrating
// from unsigned policy)
//
// 4) Validation after loading new policy from the server with a cached key
// Action: Validate as normal, and allow key rotation.
if (cached_key) {
// Case #1/#2 - loading from cache. Validate the cached key (if no key,
// then the validation will fail), then do normal policy data signature
// validation using the cached key.
// Loading from cache should not change the cached keys.
DCHECK(persisted_policy_key_.empty() ||
persisted_policy_key_ == cached_key->signing_key());
DLOG_IF(WARNING, !cached_key->has_signing_key()) <<
"Unsigned policy blob detected";
validator->ValidateCachedKey(cached_key->signing_key(),
cached_key->signing_key_signature(),
owning_domain);
// Loading from cache, so don't allow key rotation.
validator->ValidateSignature(cached_key->signing_key());
} else {
// No passed cached_key - this is not validating the initial policy load
// from cache, but rather an update from the server.
if (persisted_policy_key_.empty()) {
// Case #3 - no valid existing policy key (either this is the initial
// policy fetch, or we're doing a key rotation), so this new policy fetch
// should include an initial key provision.
validator->ValidateInitialKey(owning_domain);
} else {
// Case #4 - verify new policy with existing key. We always allow key
// rotation - the verification key will prevent invalid policy from being
// injected. |persisted_policy_key_| is already known to be valid, so no
// need to verify via ValidateCachedKey().
validator->ValidateSignatureAllowingRotation(persisted_policy_key_,
owning_domain);
}
}
ValidateKeyAndSignature(validator.get(), cached_key.get(), owning_domain);
if (validate_in_background) {
// Start validation in the background.
......
......@@ -60,6 +60,12 @@ class POLICY_EXPORT DesktopCloudPolicyStore : public UserCloudPolicyStoreBase {
bool validate_in_background,
const UserCloudPolicyValidator::CompletionCallback& callback) = 0;
// Validate the |cached_key| with the |owning_domain|.
void ValidateKeyAndSignature(
UserCloudPolicyValidator* validator,
const enterprise_management::PolicySigningKey* cached_key,
const std::string& owning_domain);
// Callback invoked to install a just-loaded policy after validation has
// finished.
void InstallLoadedPolicyAfterValidation(bool doing_key_rotation,
......
......@@ -34,7 +34,7 @@ class POLICY_EXPORT UserCloudPolicyStoreBase : public CloudPolicyStore {
protected:
// Creates a validator configured to validate a user policy. The caller owns
// the resulting object until StartValidation() is invoked.
std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
virtual std::unique_ptr<UserCloudPolicyValidator> CreateValidator(
std::unique_ptr<enterprise_management::PolicyFetchResponse> policy,
CloudPolicyValidatorBase::ValidateTimestampOption option);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment