| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/net/stub_resolver_config_reader.h" |
| |
| #include <string> |
| |
| #include "base/enterprise_util.h" |
| #include "base/feature_list.h" |
| #include "base/memory/raw_ptr.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/values.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/net/secure_dns_config.h" |
| #include "chrome/browser/net/system_network_context_manager.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/policy/core/browser/browser_policy_connector.h" |
| #include "components/policy/core/common/mock_configuration_policy_provider.h" |
| #include "components/policy/core/common/policy_map.h" |
| #include "components/policy/core/common/policy_types.h" |
| #include "components/policy/policy_constants.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "net/base/features.h" |
| #include "net/dns/public/secure_dns_mode.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| #if BUILDFLAG(IS_WIN) |
| #include "base/win/win_util.h" |
| #endif |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| #include "chrome/browser/ash/net/secure_dns_manager.h" |
| #endif |
| |
| // TODO(ericorth@chromium.org): Consider validating that the expected |
| // configuration makes it all the way to the net::HostResolverManager in the |
| // network service, rather than just testing StubResolverConfigReader output. |
| |
| namespace { |
| |
| const std::string kDnsOverHttpsTemplatesPrefName = |
| prefs::kDnsOverHttpsTemplates; |
| |
| class StubResolverConfigReaderBrowsertest |
| : public InProcessBrowserTest, |
| public testing::WithParamInterface<bool> { |
| public: |
| StubResolverConfigReaderBrowsertest() { |
| scoped_feature_list_.InitWithFeatureState(net::features::kAsyncDns, |
| GetParam()); |
| } |
| ~StubResolverConfigReaderBrowsertest() override = default; |
| |
| void SetUpInProcessBrowserTestFixture() override { |
| // Normal boilerplate to setup a MockConfigurationPolicyProvider. |
| policy_provider_.SetDefaultReturns( |
| /*is_initialization_complete_return=*/true, |
| /*is_first_policy_load_complete_return=*/true); |
| policy::BrowserPolicyConnector::SetPolicyProviderForTesting( |
| &policy_provider_); |
| } |
| |
| void SetUpOnMainThread() override { |
| // Set the mocked policy provider to act as if no policies are in use by |
| // updating to the initial still-empty `policy_map_`. |
| ASSERT_TRUE(policy_map_.empty()); |
| policy_provider_.UpdateChromePolicy(policy_map_); |
| |
| config_reader_ = SystemNetworkContextManager::GetStubResolverConfigReader(); |
| |
| // Only affects future parental controls checks, but that is all that |
| // matters here because these tests only deal with checking fresh config |
| // reads, not what has been sent to Network Service. |
| // |
| // TODO(ericorth@chromium.org): If validation of network service state is |
| // ever added, need to ensure the result of this override gets sent first. |
| config_reader_->OverrideParentalControlsForTesting( |
| /*parental_controls_override=*/false); |
| } |
| |
| protected: |
| void SetSecureDnsModePolicy(std::string mode_str) { |
| policy_map_.Set( |
| policy::key::kDnsOverHttpsMode, policy::POLICY_LEVEL_MANDATORY, |
| policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM, |
| base::Value(std::move(mode_str)), |
| /*external_data_fetcher=*/nullptr); |
| policy_provider_.UpdateChromePolicy(policy_map_); |
| } |
| |
| void SetDohTemplatesPolicy(std::string teplates_str) { |
| policy_map_.Set( |
| policy::key::kDnsOverHttpsTemplates, policy::POLICY_LEVEL_MANDATORY, |
| policy::POLICY_SCOPE_MACHINE, policy::POLICY_SOURCE_PLATFORM, |
| base::Value(std::move(teplates_str)), |
| /*external_data_fetcher=*/nullptr); |
| policy_provider_.UpdateChromePolicy(policy_map_); |
| } |
| |
| policy::PolicyMap policy_map_; |
| testing::NiceMock<policy::MockConfigurationPolicyProvider> policy_provider_; |
| |
| raw_ptr<StubResolverConfigReader, DanglingUntriaged> config_reader_; |
| |
| base::test::ScopedFeatureList scoped_feature_list_; |
| }; |
| |
| // Set various DoH modes and DoH template strings and make sure the settings are |
| // respected. |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, ConfigFromPrefs) { |
| bool async_dns_feature_enabled = GetParam(); |
| |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| // TODO(crbug.com/40229843): What is the correct function to use here? |
| EXPECT_FALSE(base::win::IsEnrolledToDomain()); |
| #endif |
| |
| std::string good_post_template = "https://foo.test/"; |
| std::string good_get_template = "https://bar.test/dns-query{?dns}"; |
| std::string bad_template = "dns-query{?dns}"; |
| std::string good_then_bad_template = good_get_template + " " + bad_template; |
| std::string bad_then_good_template = bad_template + " " + good_get_template; |
| std::string multiple_good_templates = |
| " " + good_get_template + " " + good_post_template + " "; |
| |
| PrefService* pref_service_for_user_settings = |
| g_browser_process->local_state(); |
| |
| #if BUILDFLAG(IS_CHROMEOS) |
| // On ChromeOS, the local_state is shared between all users so the user-set |
| // pref is stored in the profile's pref service. |
| pref_service_for_user_settings = browser()->profile()->GetPrefs(); |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode, |
| SecureDnsConfig::kModeSecure); |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| bad_template); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kSecure, secure_dns_config.mode()); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| good_post_template); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kSecure, secure_dns_config.mode()); |
| EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_post_template), |
| secure_dns_config.doh_servers()); |
| |
| pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode, |
| SecureDnsConfig::kModeAutomatic); |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| bad_template); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode()); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| good_then_bad_template); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode()); |
| EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_get_template), |
| secure_dns_config.doh_servers()); |
| |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| bad_then_good_template); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode()); |
| EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(good_get_template), |
| secure_dns_config.doh_servers()); |
| |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| multiple_good_templates); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kAutomatic, secure_dns_config.mode()); |
| EXPECT_EQ(*net::DnsOverHttpsConfig::FromString(multiple_good_templates), |
| secure_dns_config.doh_servers()); |
| |
| pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode, |
| SecureDnsConfig::kModeOff); |
| pref_service_for_user_settings->SetString(kDnsOverHttpsTemplatesPrefName, |
| good_get_template); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode()); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| |
| pref_service_for_user_settings->SetString(prefs::kDnsOverHttpsMode, |
| "no_match"); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode()); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| |
| // Test case with policy BuiltInDnsClientEnabled enabled. The DoH fields |
| // should be unaffected. |
| // The `prefs::kBuiltInDnsClientEnabled` pref is stored on local_state on all |
| // platforms (including Chrome OS). |
| g_browser_process->local_state()->Set( |
| prefs::kBuiltInDnsClientEnabled, base::Value(!async_dns_feature_enabled)); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| EXPECT_EQ(!async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(net::SecureDnsMode::kOff, secure_dns_config.mode()); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| DefaultNonSetPolicies) { |
| bool async_dns_feature_enabled = GetParam(); |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(async_dns_feature_enabled, |
| config_reader_->GetInsecureStubResolverEnabled()); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| // ChromeOS includes its own special functionality to set default policies if |
| // any policies are set. This function is not declared and cannot be invoked |
| // in non-CrOS builds. Expect these enterprise user defaults to disable DoH. |
| #if BUILDFLAG(IS_CHROMEOS) |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, SpecialPolicies) { |
| // Applies the special ChromeOS defaults to `policy_map_`. |
| policy::SetEnterpriseUsersDefaults(&policy_map_); |
| // Send the PolicyMap to the mock policy provider. |
| policy_provider_.UpdateChromePolicy(policy_map_); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| #endif // BUILDFLAG(IS_CHROMEOS) |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| DisableDohByPolicy) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SetSecureDnsModePolicy("off"); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| AutomaticModeByPolicy) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SetSecureDnsModePolicy("automatic"); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| SecureModeByPolicy) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SetSecureDnsModePolicy("secure"); |
| SetDohTemplatesPolicy("https://doh.test/"); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kSecure); |
| EXPECT_EQ(*net::DnsOverHttpsConfig::FromString("https://doh.test/"), |
| secure_dns_config.doh_servers()); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| InvalidTemplatePolicy) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SetSecureDnsModePolicy("secure"); |
| SetDohTemplatesPolicy("invalid template"); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kSecure); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| // Deterministic regression test for flaky failures seen in |
| // https://crbug.com/1326526. This induces a DNS resolution while in secure |
| // mode with zero DoH server templates to use. |
| ASSERT_TRUE(embedded_test_server()->Start()); |
| ASSERT_TRUE(ui_test_utils::NavigateToURL( |
| browser(), embedded_test_server()->GetURL("foo.example", "/"))); |
| } |
| |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, InvalidModePolicy) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| SetSecureDnsModePolicy("invalid"); |
| SetDohTemplatesPolicy("https://doh.test/"); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/false); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff); |
| // Expect empty templates if mode policy is invalid. |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| // Test that parental controls detection interacts correctly with prefs and |
| // policies. |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| ConfigFromParentalControls) { |
| // Mark as not enterprise managed. |
| #if BUILDFLAG(IS_WIN) |
| base::win::ScopedDomainStateForTesting scoped_domain(false); |
| EXPECT_FALSE(base::IsEnterpriseDevice()); |
| #endif |
| |
| config_reader_->OverrideParentalControlsForTesting( |
| /*parental_controls_override=*/true); |
| |
| // Parental controls takes precedence over regular prefs. |
| PrefService* local_state = g_browser_process->local_state(); |
| local_state->SetString(prefs::kDnsOverHttpsMode, |
| SecureDnsConfig::kModeAutomatic); |
| SecureDnsConfig secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/true); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kOff); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| |
| // Policy takes precedence over parental controls. |
| SetSecureDnsModePolicy("automatic"); |
| SetDohTemplatesPolicy(""); |
| secure_dns_config = config_reader_->GetSecureDnsConfiguration( |
| /*force_check_parental_controls_for_automatic_mode=*/true); |
| EXPECT_EQ(secure_dns_config.mode(), net::SecureDnsMode::kAutomatic); |
| EXPECT_THAT(secure_dns_config.doh_servers().servers(), testing::IsEmpty()); |
| } |
| |
| #if BUILDFLAG(IS_WIN) |
| IN_PROC_BROWSER_TEST_P(StubResolverConfigReaderBrowsertest, |
| AsyncDnsDisabledWhenZTDNSEnabled) { |
| // This test focuses on the interaction of ZTDNS with the AsyncDns feature. |
| // The StubResolverConfigReader constructor, which runs during |
| // SetUpOnMainThread, sets the *default* value of |
| // prefs::kBuiltInDnsClientEnabled based on ShouldEnableAsyncDns(). To test |
| // the effect of ZTDNS, we set the override *before* a relevant |
| // StubResolverConfigReader instance evaluates this default. We achieve this |
| // by creating a new instance after setting the override. |
| |
| // Simulate ZTDNS is ON |
| StubResolverConfigReader::SetZTDNSEnabledForTesting(true); |
| |
| // Create a new StubResolverConfigReader instance. Its constructor will use |
| // the ZTDNS override when calling ShouldEnableAsyncDns() to set the default |
| // value for prefs::kBuiltInDnsClientEnabled in the local_state's |
| // PrefRegistry. We pass 'true' for set_up_pref_defaults to ensure this |
| // happens. |
| StubResolverConfigReader test_config_reader(g_browser_process->local_state(), |
| true /* set_up_pref_defaults */); |
| |
| PrefService* local_state = g_browser_process->local_state(); |
| |
| // Case 1: AsyncDns feature is ON. |
| // With ZTDNS enabled (mocked), ShouldEnableAsyncDns() should return false. |
| // Therefore, the default for kBuiltInDnsClientEnabled should be false. |
| // Case 2: AsyncDns feature is OFF. |
| // ShouldEnableAsyncDns() should return false (regardless of ZTDNS). |
| // Therefore, the default for kBuiltInDnsClientEnabled should be false. |
| EXPECT_FALSE(local_state->GetBoolean(prefs::kBuiltInDnsClientEnabled)) |
| << "kBuiltInDnsClientEnabled should default to false when AsyncDns " |
| "feature is off."; |
| EXPECT_FALSE(test_config_reader.GetInsecureStubResolverEnabled()) |
| << "GetInsecureStubResolverEnabled should be false when AsyncDns " |
| "feature is off."; |
| |
| // Sanity check SecureDnsConfig (DoH settings should be unaffected by this |
| // specific test, assuming default DoH mode 'automatic' and no templates, |
| // leading to 'off' effectively unless other conditions for 'automatic' are |
| // met, which are not the focus here). This part primarily ensures |
| // GetSecureDnsConfiguration can be called without crashing. |
| SecureDnsConfig secure_dns_config = |
| test_config_reader.GetSecureDnsConfiguration( |
| false /* force_check_parental_controls_for_automatic_mode */); |
| // Default DoH mode is kAutomatic, which without explicit templates often |
| // resolves to effectively off for the purpose of doh_servers(), unless probes |
| // succeed. The exact mode isn't the primary assert here, but rather that |
| // GetInsecureStubResolverEnabled is correct. Depending on default DoH |
| // settings, mode might be kAutomatic or kOff. |
| EXPECT_TRUE(secure_dns_config.mode() == net::SecureDnsMode::kAutomatic || |
| secure_dns_config.mode() == net::SecureDnsMode::kOff); |
| } |
| #endif |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| StubResolverConfigReaderBrowsertest, |
| ::testing::Bool()); |
| |
| } // namespace |