feat: add app.configureHostResolver (#30576)

This commit is contained in:
Jeremy Rose 2021-08-31 11:55:30 -07:00 committed by GitHub
parent 3b2db5f168
commit dd7aeda6fb
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 292 additions and 7 deletions

View file

@ -1061,6 +1061,61 @@ Imports the certificate in pkcs12 format into the platform certificate store.
`callback` is called with the `result` of import operation, a value of `0`
indicates success while any other value indicates failure according to Chromium [net_error_list](https://source.chromium.org/chromium/chromium/src/+/master:net/base/net_error_list.h).
### `app.configureHostResolver(options)`
* `options` Object
* `enableBuiltInResolver` Boolean (optional) - Whether the built-in host
resolver is used in preference to getaddrinfo. When enabled, the built-in
resolver will attempt to use the system's DNS settings to do DNS lookups
itself. Enabled by default on macOS, disabled by default on Windows and
Linux.
* `secureDnsMode` String (optional) - Can be "off", "automatic" or "secure".
Configures the DNS-over-HTTP mode. When "off", no DoH lookups will be
performed. When "automatic", DoH lookups will be peformed first if DoH is
available, and insecure DNS lookups will be performed as a fallback. When
"secure", only DoH lookups will be performed. Defaults to "automatic".
* `secureDnsServers` String[] (optional) - A list of DNS-over-HTTP
server templates. See [RFC8484 § 3][] for details on the template format.
Most servers support the POST method; the template for such servers is
simply a URI. Note that for [some DNS providers][doh-providers], the
resolver will automatically upgrade to DoH unless DoH is explicitly
disabled, even if there are no DoH servers provided in this list.
* `enableAdditionalDnsQueryTypes` Boolean (optional) - Controls whether additional DNS
query types, e.g. HTTPS (DNS type 65) will be allowed besides the
traditional A and AAAA queries when a request is being made via insecure
DNS. Has no effect on Secure DNS which always allows additional types.
Defaults to true.
Configures host resolution (DNS and DNS-over-HTTPS). By default, the following
resolvers will be used, in order:
1. DNS-over-HTTPS, if the [DNS provider supports it][doh-providers], then
2. the built-in resolver (enabled on macOS only by default), then
3. the system's resolver (e.g. `getaddrinfo`).
This can be configured to either restrict usage of non-encrypted DNS
(`secureDnsMode: "secure"`), or disable DNS-over-HTTPS (`secureDnsMode:
"off"`). It is also possible to enable or disable the built-in resolver.
To disable insecure DNS, you can specify a `secureDnsMode` of `"secure"`. If you do
so, you should make sure to provide a list of DNS-over-HTTPS servers to use, in
case the user's DNS configuration does not include a provider that supports
DoH.
```js
app.configureHostResolver({
secureDnsMode: 'secure',
secureDnsServers: [
'https://cloudflare-dns.com/dns-query'
]
})
```
This API must be called after the `ready` event is emitted.
[doh-providers]: https://source.chromium.org/chromium/chromium/src/+/main:net/dns/public/doh_provider_entry.cc;l=31?q=%22DohProviderEntry::GetList()%22&ss=chromium%2Fchromium%2Fsrc
[RFC8484 § 3]: https://datatracker.ietf.org/doc/html/rfc8484#section-3
### `app.disableHardwareAcceleration()`
Disables hardware acceleration for current app.

View file

@ -4,7 +4,7 @@
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {
"@electron/docs-parser": "^0.12.1",
"@electron/docs-parser": "^0.12.2",
"@electron/typescript-definitions": "^8.9.5",
"@octokit/auth-app": "^2.10.0",
"@octokit/rest": "^18.0.3",
@ -141,4 +141,4 @@
"node script/gen-hunspell-filenames.js"
]
}
}
}

View file

@ -19,6 +19,7 @@
#include "base/system/sys_info.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/icon_manager.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "content/browser/gpu/compositor_util.h" // nogncheck
#include "content/browser/gpu/gpu_data_manager_impl.h" // nogncheck
@ -27,13 +28,16 @@
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/client_certificate_delegate.h"
#include "content/public/browser/gpu_data_manager.h"
#include "content/public/browser/network_service_instance.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/content_switches.h"
#include "media/audio/audio_manager.h"
#include "net/dns/public/util.h"
#include "net/ssl/client_cert_identity.h"
#include "net/ssl/ssl_cert_request_info.h"
#include "net/ssl/ssl_private_key.h"
#include "sandbox/policy/switches.h"
#include "services/network/network_service.h"
#include "shell/browser/api/electron_api_menu.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/api/electron_api_web_contents.h"
@ -419,6 +423,27 @@ struct Converter<content::CertificateRequestResultType> {
}
};
template <>
struct Converter<net::SecureDnsMode> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
net::SecureDnsMode* out) {
std::string s;
if (!ConvertFromV8(isolate, val, &s))
return false;
if (s == "off") {
*out = net::SecureDnsMode::kOff;
return true;
} else if (s == "automatic") {
*out = net::SecureDnsMode::kAutomatic;
return true;
} else if (s == "secure") {
*out = net::SecureDnsMode::kSecure;
return true;
}
return false;
}
};
} // namespace gin
namespace electron {
@ -1525,6 +1550,108 @@ v8::Local<v8::Value> App::GetDockAPI(v8::Isolate* isolate) {
}
#endif
void ConfigureHostResolver(v8::Isolate* isolate,
const gin_helper::Dictionary& opts) {
gin_helper::ErrorThrower thrower(isolate);
net::SecureDnsMode secure_dns_mode = net::SecureDnsMode::kOff;
std::string default_doh_templates;
if (base::FeatureList::IsEnabled(features::kDnsOverHttps)) {
if (features::kDnsOverHttpsFallbackParam.Get()) {
secure_dns_mode = net::SecureDnsMode::kAutomatic;
} else {
secure_dns_mode = net::SecureDnsMode::kSecure;
}
default_doh_templates = features::kDnsOverHttpsTemplatesParam.Get();
}
std::string server_method;
std::vector<net::DnsOverHttpsServerConfig> dns_over_https_servers;
absl::optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
servers_mojo;
if (!default_doh_templates.empty() &&
secure_dns_mode != net::SecureDnsMode::kOff) {
for (base::StringPiece server_template :
SplitStringPiece(default_doh_templates, " ", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (!net::dns_util::IsValidDohTemplate(server_template, &server_method)) {
continue;
}
bool use_post = server_method == "POST";
dns_over_https_servers.emplace_back(std::string(server_template),
use_post);
if (!servers_mojo.has_value()) {
servers_mojo = absl::make_optional<
std::vector<network::mojom::DnsOverHttpsServerPtr>>();
}
network::mojom::DnsOverHttpsServerPtr server_mojo =
network::mojom::DnsOverHttpsServer::New();
server_mojo->server_template = std::string(server_template);
server_mojo->use_post = use_post;
servers_mojo->emplace_back(std::move(server_mojo));
}
}
bool enable_built_in_resolver =
base::FeatureList::IsEnabled(features::kAsyncDns);
bool additional_dns_query_types_enabled = true;
if (opts.Has("enableBuiltInResolver") &&
!opts.Get("enableBuiltInResolver", &enable_built_in_resolver)) {
thrower.ThrowTypeError("enableBuiltInResolver must be a boolean");
return;
}
if (opts.Has("secureDnsMode") &&
!opts.Get("secureDnsMode", &secure_dns_mode)) {
thrower.ThrowTypeError(
"secureDnsMode must be one of: off, automatic, secure");
return;
}
std::vector<std::string> secure_dns_server_strings;
if (opts.Has("secureDnsServers")) {
if (!opts.Get("secureDnsServers", &secure_dns_server_strings)) {
thrower.ThrowTypeError("secureDnsServers must be an array of strings");
return;
}
servers_mojo = absl::nullopt;
for (const std::string& server_template : secure_dns_server_strings) {
std::string server_method;
if (!net::dns_util::IsValidDohTemplate(server_template, &server_method)) {
thrower.ThrowTypeError(std::string("not a valid DoH template: ") +
server_template);
return;
}
bool use_post = server_method == "POST";
if (!servers_mojo.has_value()) {
servers_mojo = absl::make_optional<
std::vector<network::mojom::DnsOverHttpsServerPtr>>();
}
network::mojom::DnsOverHttpsServerPtr server_mojo =
network::mojom::DnsOverHttpsServer::New();
server_mojo->server_template = std::string(server_template);
server_mojo->use_post = use_post;
servers_mojo->emplace_back(std::move(server_mojo));
}
}
if (opts.Has("enableAdditionalDnsQueryTypes") &&
!opts.Get("enableAdditionalDnsQueryTypes",
&additional_dns_query_types_enabled)) {
thrower.ThrowTypeError("enableAdditionalDnsQueryTypes must be a boolean");
return;
}
// Configure the stub resolver. This must be done after the system
// NetworkContext is created, but before anything has the chance to use it.
content::GetNetworkService()->ConfigureStubHostResolver(
enable_built_in_resolver, secure_dns_mode, std::move(servers_mojo),
additional_dns_query_types_enabled);
}
// static
App* App::Get() {
static base::NoDestructor<App> app;
@ -1671,6 +1798,7 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
#endif
.SetProperty("userAgentFallback", &App::GetUserAgentFallback,
&App::SetUserAgentFallback)
.SetMethod("configureHostResolver", &ConfigureHostResolver)
.SetMethod("enableSandbox", &App::EnableSandbox);
}

View file

@ -7,11 +7,14 @@
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/command_line.h"
#include "base/path_service.h"
#include "base/strings/string_split.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/net/chrome_mojo_proxy_resolver_factory.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "components/os_crypt/os_crypt.h"
@ -21,6 +24,7 @@
#include "content/public/common/network_service_util.h"
#include "electron/fuses.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/dns/public/util.h"
#include "net/net_buildflags.h"
#include "services/cert_verifier/public/mojom/cert_verifier_service_factory.mojom.h"
#include "services/network/network_service.h"
@ -234,6 +238,52 @@ void SystemNetworkContextManager::OnNetworkServiceCreated(
network_context_.BindNewPipeAndPassReceiver(),
CreateNetworkContextParams());
net::SecureDnsMode default_secure_dns_mode = net::SecureDnsMode::kOff;
std::string default_doh_templates;
if (base::FeatureList::IsEnabled(features::kDnsOverHttps)) {
if (features::kDnsOverHttpsFallbackParam.Get()) {
default_secure_dns_mode = net::SecureDnsMode::kAutomatic;
} else {
default_secure_dns_mode = net::SecureDnsMode::kSecure;
}
default_doh_templates = features::kDnsOverHttpsTemplatesParam.Get();
}
std::string server_method;
absl::optional<std::vector<network::mojom::DnsOverHttpsServerPtr>>
servers_mojo;
if (!default_doh_templates.empty() &&
default_secure_dns_mode != net::SecureDnsMode::kOff) {
for (base::StringPiece server_template :
SplitStringPiece(default_doh_templates, " ", base::TRIM_WHITESPACE,
base::SPLIT_WANT_NONEMPTY)) {
if (!net::dns_util::IsValidDohTemplate(server_template, &server_method)) {
continue;
}
bool use_post = server_method == "POST";
if (!servers_mojo.has_value()) {
servers_mojo = absl::make_optional<
std::vector<network::mojom::DnsOverHttpsServerPtr>>();
}
network::mojom::DnsOverHttpsServerPtr server_mojo =
network::mojom::DnsOverHttpsServer::New();
server_mojo->server_template = std::string(server_template);
server_mojo->use_post = use_post;
servers_mojo->emplace_back(std::move(server_mojo));
}
}
bool additional_dns_query_types_enabled = true;
// Configure the stub resolver. This must be done after the system
// NetworkContext is created, but before anything has the chance to use it.
content::GetNetworkService()->ConfigureStubHostResolver(
base::FeatureList::IsEnabled(features::kAsyncDns),
default_secure_dns_mode, std::move(servers_mojo),
additional_dns_query_types_enabled);
std::string app_name = electron::Browser::Get()->GetName();
#if defined(OS_MAC)
KeychainPassword::GetServiceName() = app_name + " Safe Storage";

View file

@ -6,7 +6,7 @@ import * as net from 'net';
import * as fs from 'fs';
import * as path from 'path';
import { promisify } from 'util';
import { app, BrowserWindow, Menu, session } from 'electron/main';
import { app, BrowserWindow, Menu, session, net as electronNet } from 'electron/main';
import { emittedOnce } from './events-helpers';
import { closeWindow, closeAllWindows } from './window-helpers';
import { ifdescribe, ifit } from './spec-helpers';
@ -1631,6 +1631,58 @@ describe('app module', () => {
expect(app.isSecureKeyboardEntryEnabled()).to.equal(false);
});
});
describe('configureHostResolver', () => {
after(() => {
// Returns to the default configuration.
app.configureHostResolver({});
});
it('fails on bad arguments', () => {
expect(() => {
(app.configureHostResolver as any)();
}).to.throw();
expect(() => {
app.configureHostResolver({
secureDnsMode: 'notAValidValue' as any
});
}).to.throw();
expect(() => {
app.configureHostResolver({
secureDnsServers: [123 as any]
});
}).to.throw();
});
it('affects dns lookup behavior', async () => {
// 1. resolve a domain name to check that things are working
await expect(new Promise((resolve, reject) => {
electronNet.request({
method: 'HEAD',
url: 'https://www.electronjs.org'
}).on('response', resolve)
.on('error', reject)
.end();
})).to.eventually.be.fulfilled();
// 2. change the host resolver configuration to something that will
// always fail
app.configureHostResolver({
secureDnsMode: 'secure',
secureDnsServers: ['https://127.0.0.1:1234']
});
// 3. check that resolving domain names now fails
await expect(new Promise((resolve, reject) => {
electronNet.request({
method: 'HEAD',
// Needs to be a slightly different domain to above, otherwise the
// response will come from the cache.
url: 'https://electronjs.org'
}).on('response', resolve)
.on('error', reject)
.end();
})).to.eventually.be.rejectedWith(/ERR_NAME_NOT_RESOLVED/);
});
});
});
describe('default behavior', () => {

View file

@ -18,10 +18,10 @@
esutils "^2.0.2"
js-tokens "^4.0.0"
"@electron/docs-parser@^0.12.1":
version "0.12.1"
resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.12.1.tgz#254c324b5953c67cdcce0a8902736778a1788742"
integrity sha512-E9/GjNVlFzBM2MNOoLjiKSE0xAMM3KsxvzMKmMeORY7aDbalObFm23XCh8DC8Jn/hfh6BzgVPF3OZO9hKvs5nw==
"@electron/docs-parser@^0.12.2":
version "0.12.2"
resolved "https://registry.yarnpkg.com/@electron/docs-parser/-/docs-parser-0.12.2.tgz#42ac92404058411be4155b25320b96192da85ba5"
integrity sha512-81l/jlz21VvTOZ21NyY1gd63ZPT/Ny0vY/nu9iYb2FkaGThMvy2xKNHifPcOTDkT+94jK0D8f7eUMDh75FIqCw==
dependencies:
"@types/markdown-it" "^10.0.0"
chai "^4.2.0"