blob: 5c2278ff3fe080e9d22b9e723e29c4748353cdb9 [file] [log] [blame]
Andreea Costinas942284d2020-01-28 16:28:40 +01001// Copyright 2020 The Chromium OS Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4#include "system-proxy/system_proxy_adaptor.h"
5
6#include <string>
7#include <utility>
8#include <vector>
9
Qijiang Fan713061e2021-03-08 15:45:12 +090010#include <base/check.h>
11#include <base/check_op.h>
Andreea Costinas942284d2020-01-28 16:28:40 +010012#include <base/location.h>
Andreea Costinase9c73592020-07-17 15:27:54 +020013#include <base/strings/stringprintf.h>
Andreea Costinas91f75352020-07-08 14:47:47 +020014#include <base/time/time.h>
Andreea Costinas942284d2020-01-28 16:28:40 +010015#include <brillo/dbus/dbus_object.h>
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010016#include <brillo/message_loops/message_loop.h>
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020017#include <chromeos/dbus/service_constants.h>
Jason Jeremy Imanadffbcb2020-08-31 13:21:36 +090018#include <chromeos/patchpanel/dbus/client.h>
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020019#include <dbus/object_proxy.h>
Andreea Costinas942284d2020-01-28 16:28:40 +010020
Andreea Costinas922fbaf2020-05-28 11:55:22 +020021#include "system-proxy/kerberos_client.h"
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010022#include "system-proxy/sandboxed_worker.h"
Andreea Costinas942284d2020-01-28 16:28:40 +010023
24namespace system_proxy {
25namespace {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010026
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020027constexpr int kProxyPort = 3128;
Andreea Costinas77b180e2020-05-12 15:17:32 +020028constexpr char kFailedToStartWorkerError[] = "Failed to start worker process";
Andreea Costinas91f75352020-07-08 14:47:47 +020029// Time delay for calling patchpanel::ConnectNamespace(). Patchpanel needs to
30// enter the network namespace of the worker process to configure it and fails
31// if it's soon after the process starts. See https://crbug.com/1095170 for
32// details.
33constexpr base::TimeDelta kConnectNamespaceDelay =
34 base::TimeDelta::FromSeconds(1);
35constexpr int kNetworkNamespaceReconnectAttempts = 3;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +020036
Andreea Costinas942284d2020-01-28 16:28:40 +010037// Serializes |proto| to a vector of bytes.
38std::vector<uint8_t> SerializeProto(
39 const google::protobuf::MessageLite& proto) {
40 std::vector<uint8_t> proto_blob(proto.ByteSizeLong());
Andreea Costinasc991e232020-06-08 20:30:58 +020041 bool result = proto.SerializeToArray(proto_blob.data(), proto_blob.size());
42 DCHECK(result);
Andreea Costinas942284d2020-01-28 16:28:40 +010043 return proto_blob;
44}
45
46// Parses a proto from an array of bytes |proto_blob|. Returns
47// ERROR_PARSE_REQUEST_FAILED on error.
48std::string DeserializeProto(const base::Location& from_here,
49 google::protobuf::MessageLite* proto,
50 const std::vector<uint8_t>& proto_blob) {
51 if (!proto->ParseFromArray(proto_blob.data(), proto_blob.size())) {
52 const std::string error_message = "Failed to parse proto message.";
53 LOG(ERROR) << from_here.ToString() << error_message;
54 return error_message;
55 }
56 return "";
57}
58} // namespace
59
60SystemProxyAdaptor::SystemProxyAdaptor(
61 std::unique_ptr<brillo::dbus_utils::DBusObject> dbus_object)
62 : org::chromium::SystemProxyAdaptor(this),
Andreea Costinas91f75352020-07-08 14:47:47 +020063 netns_reconnect_attempts_available_(kNetworkNamespaceReconnectAttempts),
Andreea Costinasc7d5ad02020-03-09 09:41:51 +010064 dbus_object_(std::move(dbus_object)),
Andreea Costinas922fbaf2020-05-28 11:55:22 +020065 weak_ptr_factory_(this) {
66 kerberos_client_ = std::make_unique<KerberosClient>(dbus_object_->GetBus());
67}
Andreea Costinas942284d2020-01-28 16:28:40 +010068
69SystemProxyAdaptor::~SystemProxyAdaptor() = default;
70
71void SystemProxyAdaptor::RegisterAsync(
72 const brillo::dbus_utils::AsyncEventSequencer::CompletionAction&
73 completion_callback) {
74 RegisterWithDBusObject(dbus_object_.get());
75 dbus_object_->RegisterAsync(completion_callback);
76}
77
Andreea Costinas77b180e2020-05-12 15:17:32 +020078std::vector<uint8_t> SystemProxyAdaptor::SetAuthenticationDetails(
79 const std::vector<uint8_t>& request_blob) {
80 LOG(INFO) << "Received set authentication details request.";
81
82 SetAuthenticationDetailsRequest request;
Andreea Costinas350e4aa2020-07-20 20:29:46 +020083 std::string error_message =
Andreea Costinas77b180e2020-05-12 15:17:32 +020084 DeserializeProto(FROM_HERE, &request, request_blob);
85
86 SetAuthenticationDetailsResponse response;
87 if (!error_message.empty()) {
88 response.set_error_message(error_message);
89 return SerializeProto(response);
90 }
91
Andreea Costinas350e4aa2020-07-20 20:29:46 +020092 if (IncludesSystemTraffic(request.traffic_type())) {
93 SetAuthenticationDetails(request, /*user_traffic=*/false, &error_message);
94 }
95 if (IncludesUserTraffic(request.traffic_type())) {
96 SetAuthenticationDetails(request, /*user_traffic=*/true, &error_message);
97 }
98 if (!error_message.empty()) {
99 response.set_error_message(error_message);
100 }
101 return SerializeProto(response);
102}
103
104void SystemProxyAdaptor::SetAuthenticationDetails(
105 SetAuthenticationDetailsRequest auth_details,
106 bool user_traffic,
107 std::string* error_message) {
108 SandboxedWorker* worker = CreateWorkerIfNeeded(user_traffic);
109 if (!worker) {
110 error_message->append(kFailedToStartWorkerError);
111 return;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200112 }
113
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200114 if (auth_details.has_credentials() || auth_details.has_protection_space()) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200115 worker::Credentials credentials;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200116 if (auth_details.has_protection_space()) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200117 worker::ProtectionSpace protection_space;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200118 protection_space.set_origin(auth_details.protection_space().origin());
119 protection_space.set_scheme(auth_details.protection_space().scheme());
120 protection_space.set_realm(auth_details.protection_space().realm());
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200121 *credentials.mutable_protection_space() = protection_space;
122 }
Andreea Costinas77b180e2020-05-12 15:17:32 +0200123
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200124 if (auth_details.has_credentials()) {
Andreea Costinascc4d54e2020-10-19 15:46:25 +0200125 system_proxy::Credentials dbus_cred = auth_details.credentials();
126 if (dbus_cred.has_username() && dbus_cred.has_password()) {
127 credentials.set_username(dbus_cred.username());
128 credentials.set_password(dbus_cred.password());
129 credentials.mutable_policy_credentials_auth_schemes()->Swap(
130 dbus_cred.mutable_policy_credentials_auth_schemes());
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200131 }
132 }
133
134 brillo::MessageLoop::current()->PostTask(
135 FROM_HERE,
136 base::Bind(&SystemProxyAdaptor::SetCredentialsTask,
137 weak_ptr_factory_.GetWeakPtr(), worker, credentials));
138 }
139 if (auth_details.has_kerberos_enabled()) {
140 std::string principal_name = auth_details.has_active_principal_name()
141 ? auth_details.active_principal_name()
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200142 : std::string();
143
144 brillo::MessageLoop::current()->PostTask(
145 FROM_HERE, base::Bind(&SystemProxyAdaptor::SetKerberosEnabledTask,
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200146 weak_ptr_factory_.GetWeakPtr(), worker,
147 auth_details.kerberos_enabled(), principal_name));
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200148 }
Andreea Costinas77b180e2020-05-12 15:17:32 +0200149}
150
Andreea Costinase9c73592020-07-17 15:27:54 +0200151std::vector<uint8_t> SystemProxyAdaptor::ClearUserCredentials(
152 const std::vector<uint8_t>& request_blob) {
153 LOG(INFO) << "Received request to clear user credentials.";
154 std::string error_message;
155 ClearUserCredentials(/*user_traffic=*/false, &error_message);
156 ClearUserCredentials(/*user_traffic=*/true, &error_message);
157
158 ClearUserCredentialsResponse response;
159 if (!error_message.empty())
160 response.set_error_message(error_message);
161 return SerializeProto(response);
162}
163
164void SystemProxyAdaptor::ClearUserCredentials(bool user_traffic,
165 std::string* error_message) {
166 SandboxedWorker* worker = GetWorker(user_traffic);
167 if (!worker) {
168 return;
169 }
170 if (!worker->ClearUserCredentials()) {
171 error_message->append(
172 base::StringPrintf("Failure to clear user credentials for worker with "
173 "pid %s. Restarting worker.",
174 std::to_string(worker->pid()).c_str()));
175 ResetWorker(user_traffic);
176 CreateWorkerIfNeeded(user_traffic);
177 }
178}
179
Andreea Costinasfc3dc7d2020-07-20 18:54:38 +0200180std::vector<uint8_t> SystemProxyAdaptor::ShutDownProcess(
181 const std::vector<uint8_t>& request_blob) {
182 LOG(INFO) << "Received shutdown request.";
183 ShutDownRequest request;
184 std::string error_message =
185 DeserializeProto(FROM_HERE, &request, request_blob);
186
187 if (IncludesSystemTraffic(request.traffic_type()) &&
188 !ResetWorker(/* user_traffic=*/false)) {
189 error_message =
190 "Failure to terminate worker process for system services traffic.";
191 }
192
193 if (IncludesUserTraffic(request.traffic_type()) &&
194 !ResetWorker(/* user_traffic=*/true)) {
195 error_message += "Failure to terminate worker process for arc traffic.";
196 }
197
198 ShutDownResponse response;
199 if (!error_message.empty())
200 response.set_error_message(error_message);
201
202 if (request.traffic_type() == TrafficOrigin::ALL) {
203 brillo::MessageLoop::current()->PostTask(
204 FROM_HERE, base::Bind(&SystemProxyAdaptor::ShutDownTask,
205 weak_ptr_factory_.GetWeakPtr()));
206 }
207 return SerializeProto(response);
208}
209
Andreea Costinas5862b102020-03-19 14:45:36 +0100210void SystemProxyAdaptor::GetChromeProxyServersAsync(
211 const std::string& target_url,
212 const brillo::http::GetChromeProxyServersCallback& callback) {
Andreea Costinasc9defae2020-04-22 10:28:35 +0200213 brillo::http::GetChromeProxyServersAsync(dbus_object_->GetBus(), target_url,
214 move(callback));
Andreea Costinas5862b102020-03-19 14:45:36 +0100215}
216
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200217bool SystemProxyAdaptor::IsLocalProxy(const std::string& proxy) {
218 if (system_services_worker_ &&
219 proxy.find(system_services_worker_->local_proxy_host_and_port()) !=
220 std::string::npos) {
221 return true;
222 }
223 return arc_worker_ && proxy.find(arc_worker_->local_proxy_host_and_port()) !=
224 std::string::npos;
225}
226
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100227std::unique_ptr<SandboxedWorker> SystemProxyAdaptor::CreateWorker() {
Andreea Costinas5862b102020-03-19 14:45:36 +0100228 return std::make_unique<SandboxedWorker>(weak_ptr_factory_.GetWeakPtr());
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100229}
230
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200231SandboxedWorker* SystemProxyAdaptor::CreateWorkerIfNeeded(bool user_traffic) {
232 SandboxedWorker* worker = GetWorker(user_traffic);
233 if (worker) {
234 // A worker for traffic indicated by |user_traffic| already exists.
235 return worker;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200236 }
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200237 SetWorker(user_traffic, CreateWorker());
238 worker = GetWorker(user_traffic);
Andreea Costinas77b180e2020-05-12 15:17:32 +0200239
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200240 if (!worker->Start()) {
241 ResetWorker(user_traffic);
242 return nullptr;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200243 }
244 // patchpanel_proxy is owned by |dbus_object_->bus_|.
245 dbus::ObjectProxy* patchpanel_proxy = dbus_object_->GetBus()->GetObjectProxy(
246 patchpanel::kPatchPanelServiceName,
247 dbus::ObjectPath(patchpanel::kPatchPanelServicePath));
248 patchpanel_proxy->WaitForServiceToBeAvailable(
249 base::Bind(&SystemProxyAdaptor::OnPatchpanelServiceAvailable,
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200250 weak_ptr_factory_.GetWeakPtr(), user_traffic));
251 return worker;
Andreea Costinas77b180e2020-05-12 15:17:32 +0200252}
253
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200254void SystemProxyAdaptor::SetCredentialsTask(
255 SandboxedWorker* worker, const worker::Credentials& credentials) {
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100256 DCHECK(worker);
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200257 worker->SetCredentials(credentials);
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100258}
259
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200260void SystemProxyAdaptor::SetKerberosEnabledTask(
261 SandboxedWorker* worker,
262 bool kerberos_enabled,
263 const std::string& principal_name) {
264 DCHECK(worker);
Andreea Costinas922fbaf2020-05-28 11:55:22 +0200265 worker->SetKerberosEnabled(kerberos_enabled,
266 kerberos_client_->krb5_conf_path(),
267 kerberos_client_->krb5_ccache_path());
268 kerberos_client_->SetKerberosEnabled(kerberos_enabled);
269 if (kerberos_enabled) {
270 kerberos_client_->SetPrincipalName(principal_name);
271 }
272}
273
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100274void SystemProxyAdaptor::ShutDownTask() {
275 brillo::MessageLoop::current()->BreakLoop();
276}
277
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200278void SystemProxyAdaptor::SetWorker(bool user_traffic,
279 std::unique_ptr<SandboxedWorker> worker) {
280 if (user_traffic) {
281 arc_worker_ = std::move(worker);
282 } else {
283 system_services_worker_ = std::move(worker);
284 }
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100285}
286
Andreea Costinase9c73592020-07-17 15:27:54 +0200287bool SystemProxyAdaptor::ResetWorker(bool user_traffic) {
288 SandboxedWorker* worker =
289 user_traffic ? arc_worker_.get() : system_services_worker_.get();
290 if (!worker) {
291 return true;
292 }
293 if (!worker->Stop()) {
294 return false;
295 }
296 if (user_traffic) {
297 arc_worker_.reset();
298 } else {
299 system_services_worker_.reset();
300 }
301 return true;
302}
303
304SandboxedWorker* SystemProxyAdaptor::GetWorker(bool user_traffic) {
305 return user_traffic ? arc_worker_.get() : system_services_worker_.get();
306}
307
Andreea Costinasfc3dc7d2020-07-20 18:54:38 +0200308bool SystemProxyAdaptor::IncludesSystemTraffic(TrafficOrigin traffic_origin) {
309 return traffic_origin != TrafficOrigin::USER;
310}
311
312bool SystemProxyAdaptor::IncludesUserTraffic(TrafficOrigin traffic_origin) {
313 return traffic_origin != TrafficOrigin::SYSTEM;
314}
315
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200316void SystemProxyAdaptor::OnPatchpanelServiceAvailable(bool user_traffic,
317 bool is_available) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200318 if (!is_available) {
319 LOG(ERROR) << "Patchpanel service not available";
320 return;
321 }
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200322 ConnectNamespace(user_traffic);
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200323}
324
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200325void SystemProxyAdaptor::ConnectNamespace(bool user_traffic) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200326 DCHECK_GT(netns_reconnect_attempts_available_, 0);
327 --netns_reconnect_attempts_available_;
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200328 SandboxedWorker* worker = GetWorker(user_traffic);
329 DCHECK(worker);
Andreea Costinas91f75352020-07-08 14:47:47 +0200330 // TODO(b/160736881, acostinas): Remove the delay after patchpanel
331 // implements "ip netns" to create the veth pair across network namespaces.
332 brillo::MessageLoop::current()->PostDelayedTask(
333 FROM_HERE,
334 base::Bind(&SystemProxyAdaptor::ConnectNamespaceTask,
Andreea Costinase9c73592020-07-17 15:27:54 +0200335 weak_ptr_factory_.GetWeakPtr(), worker, user_traffic),
Andreea Costinas91f75352020-07-08 14:47:47 +0200336 kConnectNamespaceDelay);
337}
338
339void SystemProxyAdaptor::ConnectNamespaceTask(SandboxedWorker* worker,
340 bool user_traffic) {
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200341 std::unique_ptr<patchpanel::Client> patchpanel_client =
342 patchpanel::Client::New();
343 if (!patchpanel_client) {
344 LOG(ERROR) << "Failed to open networking service client";
Andreea Costinas91f75352020-07-08 14:47:47 +0200345 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200346 }
347
Garrick Evans58697022020-12-03 12:41:13 +0900348 // TODO(acostinas): The source will need to be updated to accommodate Crostini
349 // when proxy support is added.
350 auto traffic_source = user_traffic ? patchpanel::TrafficCounter::ARC
351 : patchpanel::TrafficCounter::SYSTEM;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200352 std::pair<base::ScopedFD, patchpanel::ConnectNamespaceResponse> result =
353 patchpanel_client->ConnectNamespace(
Garrick Evans58697022020-12-03 12:41:13 +0900354 worker->pid(), "" /* outbound_ifname */, user_traffic,
355 true /* route_on_vpn */, traffic_source);
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200356
357 if (!result.first.is_valid()) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200358 LOG(ERROR) << "Failed to setup network namespace on attempt "
359 << kNetworkNamespaceReconnectAttempts -
360 netns_reconnect_attempts_available_;
361 if (netns_reconnect_attempts_available_ > 0) {
Andreea Costinas350e4aa2020-07-20 20:29:46 +0200362 ConnectNamespace(user_traffic);
Andreea Costinas91f75352020-07-08 14:47:47 +0200363 }
364 return;
Andreea Costinasedb7c8e2020-04-22 10:58:04 +0200365 }
366
367 worker->SetNetNamespaceLifelineFd(std::move(result.first));
Garrick Evans20421132020-12-02 12:14:23 +0900368 if (!worker->SetListeningAddress(result.second.peer_ipv4_address(),
Andreea Costinasa89309d2020-05-08 15:51:12 +0200369 kProxyPort)) {
Andreea Costinas91f75352020-07-08 14:47:47 +0200370 return;
Andreea Costinasa89309d2020-05-08 15:51:12 +0200371 }
372 OnNamespaceConnected(worker, user_traffic);
Andreea Costinasa89309d2020-05-08 15:51:12 +0200373}
374
375void SystemProxyAdaptor::OnNamespaceConnected(SandboxedWorker* worker,
376 bool user_traffic) {
377 WorkerActiveSignalDetails details;
378 details.set_traffic_origin(user_traffic ? TrafficOrigin::USER
379 : TrafficOrigin::SYSTEM);
380 details.set_local_proxy_url(worker->local_proxy_host_and_port());
381 SendWorkerActiveSignal(SerializeProto(details));
Andreea Costinasc7d5ad02020-03-09 09:41:51 +0100382}
383
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200384void SystemProxyAdaptor::RequestAuthenticationCredentials(
Andreea Costinased9e6122020-08-12 12:06:19 +0200385 const worker::ProtectionSpace& protection_space,
386 bool bad_cached_credentials) {
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200387 AuthenticationRequiredDetails details;
388 ProtectionSpace proxy_protection_space;
389 proxy_protection_space.set_origin(protection_space.origin());
390 proxy_protection_space.set_realm(protection_space.realm());
391 proxy_protection_space.set_scheme(protection_space.scheme());
392 *details.mutable_proxy_protection_space() = proxy_protection_space;
Andreea Costinased9e6122020-08-12 12:06:19 +0200393 details.set_bad_cached_credentials(bad_cached_credentials);
Andreea Costinasdb2cbee2020-06-15 11:43:44 +0200394 SendAuthenticationRequiredSignal(SerializeProto(details));
395}
396
Andreea Costinas942284d2020-01-28 16:28:40 +0100397} // namespace system_proxy