зеркало из https://github.com/electron/electron.git
feat: add app.configureHostResolver (#30576)
This commit is contained in:
Родитель
3b2db5f168
Коммит
dd7aeda6fb
|
@ -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.
|
||||
|
|
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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";
|
||||
|
|
|
@ -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', () => {
|
||||
|
|
|
@ -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"
|
||||
|
|
Загрузка…
Ссылка в новой задаче